We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Metis is broken
```
$ spack install metis@5.0.2
==> Installing metis
==> cmake is already installed in /Users/davydden/spack/opt/spack/darwin-sierra-x86_64/clang-8.0.0-apple/cmake-3.6.1-knsjcg5wlbnsy27ja5ngp5xdftflnv4a
==> Trying to fetch from file:///Users/davydden/spack/var/spack/cache/metis/metis-5.0.2.tar.gz
curl: (37) Couldn't open file /Users/davydden/spack/var/spack/cache/metis/metis-5.0.2.tar.gz
==> Fetching from file:///Users/davydden/spack/var/spack/cache/metis/metis-5.0.2.tar.gz failed.
==> Trying to fetch from http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/metis-5.0.2.tar.gz
######################################################################## 100.0%
==> Staging archive: /Users/davydden/spack/var/spack/stage/metis-5.0.2-36gz5ygfj3o7ptkltsuh3t2cezy56g3y/metis-5.0.2.tar.gz
==> Created stage in /Users/davydden/spack/var/spack/stage/metis-5.0.2-36gz5ygfj3o7ptkltsuh3t2cezy56g3y
==> Applied patch install_gklib_defs_rename.patch
==> Ran patch() for metis
==> Building metis [Package]
==> Executing phase : 'install'
Traceback (most recent call last):
  File "/Users/davydden/spack/bin/spack", line 202, in <module>
    main()
  File "/Users/davydden/spack/bin/spack", line 179, in main
    return_val = command(parser, args)
  File "/Users/davydden/spack/lib/spack/spack/cmd/install.py", line 117, in install
    package.do_install(**kwargs)
  File "/Users/davydden/spack/lib/spack/spack/package.py", line 1268, in do_install
    spack.build_environment.fork(self, build_process, dirty=dirty)
  File "/Users/davydden/spack/lib/spack/spack/build_environment.py", line 542, in fork
    raise exception
AttributeError: 'functools.partial' object has no attribute '__module__'
```

@alalazo any guesses here? Maybe related to https://github.com/LLNL/spack/pull/1186. But I don't see any clues in `package.py` of `metis`.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/spack/spack/multimethod.py`
Content:
```
1 ##############################################################################
2 # Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
3 # Produced at the Lawrence Livermore National Laboratory.
4 #
5 # This file is part of Spack.
6 # Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
7 # LLNL-CODE-647188
8 #
9 # For details, see https://github.com/llnl/spack
10 # Please also see the LICENSE file for our notice and the LGPL.
11 #
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU Lesser General Public License (as
14 # published by the Free Software Foundation) version 2.1, February 1999.
15 #
16 # This program is distributed in the hope that it will be useful, but
17 # WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
19 # conditions of the GNU Lesser General Public License for more details.
20 #
21 # You should have received a copy of the GNU Lesser General Public
22 # License along with this program; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 ##############################################################################
25 """This module contains utilities for using multi-methods in
26 spack. You can think of multi-methods like overloaded methods --
27 they're methods with the same name, and we need to select a version
28 of the method based on some criteria.  e.g., for overloaded
29 methods, you would select a version of the method to call based on
30 the types of its arguments.
31 
32 In spack, multi-methods are used to ease the life of package
33 authors.  They allow methods like install() (or other methods
34 called by install()) to declare multiple versions to be called when
35 the package is instantiated with different specs.  e.g., if the
36 package is built with OpenMPI on x86_64,, you might want to call a
37 different install method than if it was built for mpich2 on
38 BlueGene/Q.  Likewise, you might want to do a different type of
39 install for different versions of the package.
40 
41 Multi-methods provide a simple decorator-based syntax for this that
42 avoids overly complicated rat nests of if statements.  Obviously,
43 depending on the scenario, regular old conditionals might be clearer,
44 so package authors should use their judgement.
45 """
46 import functools
47 
48 from llnl.util.lang import *
49 
50 import spack.architecture
51 import spack.error
52 from spack.spec import parse_anonymous_spec
53 
54 
55 class SpecMultiMethod(object):
56     """This implements a multi-method for Spack specs.  Packages are
57        instantiated with a particular spec, and you may want to
58        execute different versions of methods based on what the spec
59        looks like.  For example, you might want to call a different
60        version of install() for one platform than you call on another.
61 
62        The SpecMultiMethod class implements a callable object that
63        handles method dispatch.  When it is called, it looks through
64        registered methods and their associated specs, and it tries
65        to find one that matches the package's spec.  If it finds one
66        (and only one), it will call that method.
67 
68        The package author is responsible for ensuring that only one
69        condition on multi-methods ever evaluates to true.  If
70        multiple methods evaluate to true, this will raise an
71        exception.
72 
73        This is intended for use with decorators (see below).  The
74        decorator (see docs below) creates SpecMultiMethods and
75        registers method versions with them.
76 
77        To register a method, you can do something like this:
78            mm = SpecMultiMethod()
79            mm.register("^chaos_5_x86_64_ib", some_method)
80 
81        The object registered needs to be a Spec or some string that
82        will parse to be a valid spec.
83 
84        When the mm is actually called, it selects a version of the
85        method to call based on the sys_type of the object it is
86        called on.
87 
88        See the docs for decorators below for more details.
89     """
90 
91     def __init__(self, default=None):
92         self.method_list = []
93         self.default = default
94         if default:
95             functools.update_wrapper(self, default)
96 
97     def register(self, spec, method):
98         """Register a version of a method for a particular sys_type."""
99         self.method_list.append((spec, method))
100 
101         if not hasattr(self, '__name__'):
102             functools.update_wrapper(self, method)
103         else:
104             assert(self.__name__ == method.__name__)
105 
106     def __get__(self, obj, objtype):
107         """This makes __call__ support instance methods."""
108         return functools.partial(self.__call__, obj)
109 
110     def __call__(self, package_self, *args, **kwargs):
111         """Find the first method with a spec that matches the
112            package's spec.  If none is found, call the default
113            or if there is none, then raise a NoSuchMethodError.
114         """
115         for spec, method in self.method_list:
116             if package_self.spec.satisfies(spec):
117                 return method(package_self, *args, **kwargs)
118 
119         if self.default:
120             return self.default(package_self, *args, **kwargs)
121         else:
122             raise NoSuchMethodError(
123                 type(package_self), self.__name__, spec,
124                 [m[0] for m in self.method_list])
125 
126     def __str__(self):
127         return "SpecMultiMethod {\n\tdefault: %s,\n\tspecs: %s\n}" % (
128             self.default, self.method_list)
129 
130 
131 class when(object):
132     """This annotation lets packages declare multiple versions of
133        methods like install() that depend on the package's spec.
134        For example:
135 
136        .. code-block:: python
137 
138           class SomePackage(Package):
139               ...
140 
141               def install(self, prefix):
142                   # Do default install
143 
144               @when('arch=chaos_5_x86_64_ib')
145               def install(self, prefix):
146                   # This will be executed instead of the default install if
147                   # the package's platform() is chaos_5_x86_64_ib.
148 
149               @when('arch=bgqos_0")
150               def install(self, prefix):
151                   # This will be executed if the package's sys_type is bgqos_0
152 
153        This allows each package to have a default version of install() AND
154        specialized versions for particular platforms.  The version that is
155        called depends on the architecutre of the instantiated package.
156 
157        Note that this works for methods other than install, as well.  So,
158        if you only have part of the install that is platform specific, you
159        could do this:
160 
161        .. code-block:: python
162 
163           class SomePackage(Package):
164               ...
165               # virtual dependence on MPI.
166               # could resolve to mpich, mpich2, OpenMPI
167               depends_on('mpi')
168 
169               def setup(self):
170                   # do nothing in the default case
171                   pass
172 
173               @when('^openmpi')
174               def setup(self):
175                   # do something special when this is built with OpenMPI for
176                   # its MPI implementations.
177 
178 
179               def install(self, prefix):
180                   # Do common install stuff
181                   self.setup()
182                   # Do more common install stuff
183 
184        There must be one (and only one) @when clause that matches the
185        package's spec.  If there is more than one, or if none match,
186        then the method will raise an exception when it's called.
187 
188        Note that the default version of decorated methods must
189        *always* come first.  Otherwise it will override all of the
190        platform-specific versions.  There's not much we can do to get
191        around this because of the way decorators work.
192     """
193 
194     def __init__(self, spec):
195         pkg = get_calling_module_name()
196         if spec is True:
197             spec = pkg
198         self.spec = (parse_anonymous_spec(spec, pkg)
199                      if spec is not False else None)
200 
201     def __call__(self, method):
202         # Get the first definition of the method in the calling scope
203         original_method = caller_locals().get(method.__name__)
204 
205         # Create a multimethod out of the original method if it
206         # isn't one already.
207         if not type(original_method) == SpecMultiMethod:
208             original_method = SpecMultiMethod(original_method)
209 
210         if self.spec is not None:
211             original_method.register(self.spec, method)
212 
213         return original_method
214 
215 
216 class MultiMethodError(spack.error.SpackError):
217     """Superclass for multimethod dispatch errors"""
218 
219     def __init__(self, message):
220         super(MultiMethodError, self).__init__(message)
221 
222 
223 class NoSuchMethodError(spack.error.SpackError):
224     """Raised when we can't find a version of a multi-method."""
225 
226     def __init__(self, cls, method_name, spec, possible_specs):
227         super(NoSuchMethodError, self).__init__(
228             "Package %s does not support %s called with %s.  Options are: %s"
229             % (cls.__name__, method_name, spec,
230                ", ".join(str(s) for s in possible_specs)))
231
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py
--- a/lib/spack/spack/multimethod.py
+++ b/lib/spack/spack/multimethod.py
@@ -105,7 +105,17 @@
 
     def __get__(self, obj, objtype):
         """This makes __call__ support instance methods."""
-        return functools.partial(self.__call__, obj)
+        # Method_list is a list of tuples (constraint, method)
+        # Here we are going to assume that we have at least one
+        # element in the list. The first registered function
+        # will be the one 'wrapped'.
+        wrapped_method = self.method_list[0][1]
+        # Call functools.wraps manually to get all the attributes
+        # we need to be disguised as the wrapped_method
+        func = functools.wraps(wrapped_method)(
+            functools.partial(self.__call__, obj)
+        )
+        return func
 
     def __call__(self, package_self, *args, **kwargs):
         """Find the first method with a spec that matches the

{"golden_diff": "diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py\n--- a/lib/spack/spack/multimethod.py\n+++ b/lib/spack/spack/multimethod.py\n@@ -105,7 +105,17 @@\n \n     def __get__(self, obj, objtype):\n         \"\"\"This makes __call__ support instance methods.\"\"\"\n-        return functools.partial(self.__call__, obj)\n+        # Method_list is a list of tuples (constraint, method)\n+        # Here we are going to assume that we have at least one\n+        # element in the list. The first registered function\n+        # will be the one 'wrapped'.\n+        wrapped_method = self.method_list[0][1]\n+        # Call functools.wraps manually to get all the attributes\n+        # we need to be disguised as the wrapped_method\n+        func = functools.wraps(wrapped_method)(\n+            functools.partial(self.__call__, obj)\n+        )\n+        return func\n \n     def __call__(self, package_self, *args, **kwargs):\n         \"\"\"Find the first method with a spec that matches the\n", "issue": "Metis is broken\n```\n$ spack install metis@5.0.2\n==> Installing metis\n==> cmake is already installed in /Users/davydden/spack/opt/spack/darwin-sierra-x86_64/clang-8.0.0-apple/cmake-3.6.1-knsjcg5wlbnsy27ja5ngp5xdftflnv4a\n==> Trying to fetch from file:///Users/davydden/spack/var/spack/cache/metis/metis-5.0.2.tar.gz\ncurl: (37) Couldn't open file /Users/davydden/spack/var/spack/cache/metis/metis-5.0.2.tar.gz\n==> Fetching from file:///Users/davydden/spack/var/spack/cache/metis/metis-5.0.2.tar.gz failed.\n==> Trying to fetch from http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/metis-5.0.2.tar.gz\n######################################################################## 100.0%\n==> Staging archive: /Users/davydden/spack/var/spack/stage/metis-5.0.2-36gz5ygfj3o7ptkltsuh3t2cezy56g3y/metis-5.0.2.tar.gz\n==> Created stage in /Users/davydden/spack/var/spack/stage/metis-5.0.2-36gz5ygfj3o7ptkltsuh3t2cezy56g3y\n==> Applied patch install_gklib_defs_rename.patch\n==> Ran patch() for metis\n==> Building metis [Package]\n==> Executing phase : 'install'\nTraceback (most recent call last):\n  File \"/Users/davydden/spack/bin/spack\", line 202, in <module>\n    main()\n  File \"/Users/davydden/spack/bin/spack\", line 179, in main\n    return_val = command(parser, args)\n  File \"/Users/davydden/spack/lib/spack/spack/cmd/install.py\", line 117, in install\n    package.do_install(**kwargs)\n  File \"/Users/davydden/spack/lib/spack/spack/package.py\", line 1268, in do_install\n    spack.build_environment.fork(self, build_process, dirty=dirty)\n  File \"/Users/davydden/spack/lib/spack/spack/build_environment.py\", line 542, in fork\n    raise exception\nAttributeError: 'functools.partial' object has no attribute '__module__'\n```\n\n@alalazo any guesses here? Maybe related to https://github.com/LLNL/spack/pull/1186. But I don't see any clues in `package.py` of `metis`.\n\n", "before_files": [{"content": "##############################################################################\n# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.\n# Produced at the Lawrence Livermore National Laboratory.\n#\n# This file is part of Spack.\n# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.\n# LLNL-CODE-647188\n#\n# For details, see https://github.com/llnl/spack\n# Please also see the LICENSE file for our notice and the LGPL.\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License (as\n# published by the Free Software Foundation) version 2.1, February 1999.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and\n# conditions of the GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public\n# License along with this program; if not, write to the Free Software\n# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n##############################################################################\n\"\"\"This module contains utilities for using multi-methods in\nspack. You can think of multi-methods like overloaded methods --\nthey're methods with the same name, and we need to select a version\nof the method based on some criteria.  e.g., for overloaded\nmethods, you would select a version of the method to call based on\nthe types of its arguments.\n\nIn spack, multi-methods are used to ease the life of package\nauthors.  They allow methods like install() (or other methods\ncalled by install()) to declare multiple versions to be called when\nthe package is instantiated with different specs.  e.g., if the\npackage is built with OpenMPI on x86_64,, you might want to call a\ndifferent install method than if it was built for mpich2 on\nBlueGene/Q.  Likewise, you might want to do a different type of\ninstall for different versions of the package.\n\nMulti-methods provide a simple decorator-based syntax for this that\navoids overly complicated rat nests of if statements.  Obviously,\ndepending on the scenario, regular old conditionals might be clearer,\nso package authors should use their judgement.\n\"\"\"\nimport functools\n\nfrom llnl.util.lang import *\n\nimport spack.architecture\nimport spack.error\nfrom spack.spec import parse_anonymous_spec\n\n\nclass SpecMultiMethod(object):\n    \"\"\"This implements a multi-method for Spack specs.  Packages are\n       instantiated with a particular spec, and you may want to\n       execute different versions of methods based on what the spec\n       looks like.  For example, you might want to call a different\n       version of install() for one platform than you call on another.\n\n       The SpecMultiMethod class implements a callable object that\n       handles method dispatch.  When it is called, it looks through\n       registered methods and their associated specs, and it tries\n       to find one that matches the package's spec.  If it finds one\n       (and only one), it will call that method.\n\n       The package author is responsible for ensuring that only one\n       condition on multi-methods ever evaluates to true.  If\n       multiple methods evaluate to true, this will raise an\n       exception.\n\n       This is intended for use with decorators (see below).  The\n       decorator (see docs below) creates SpecMultiMethods and\n       registers method versions with them.\n\n       To register a method, you can do something like this:\n           mm = SpecMultiMethod()\n           mm.register(\"^chaos_5_x86_64_ib\", some_method)\n\n       The object registered needs to be a Spec or some string that\n       will parse to be a valid spec.\n\n       When the mm is actually called, it selects a version of the\n       method to call based on the sys_type of the object it is\n       called on.\n\n       See the docs for decorators below for more details.\n    \"\"\"\n\n    def __init__(self, default=None):\n        self.method_list = []\n        self.default = default\n        if default:\n            functools.update_wrapper(self, default)\n\n    def register(self, spec, method):\n        \"\"\"Register a version of a method for a particular sys_type.\"\"\"\n        self.method_list.append((spec, method))\n\n        if not hasattr(self, '__name__'):\n            functools.update_wrapper(self, method)\n        else:\n            assert(self.__name__ == method.__name__)\n\n    def __get__(self, obj, objtype):\n        \"\"\"This makes __call__ support instance methods.\"\"\"\n        return functools.partial(self.__call__, obj)\n\n    def __call__(self, package_self, *args, **kwargs):\n        \"\"\"Find the first method with a spec that matches the\n           package's spec.  If none is found, call the default\n           or if there is none, then raise a NoSuchMethodError.\n        \"\"\"\n        for spec, method in self.method_list:\n            if package_self.spec.satisfies(spec):\n                return method(package_self, *args, **kwargs)\n\n        if self.default:\n            return self.default(package_self, *args, **kwargs)\n        else:\n            raise NoSuchMethodError(\n                type(package_self), self.__name__, spec,\n                [m[0] for m in self.method_list])\n\n    def __str__(self):\n        return \"SpecMultiMethod {\\n\\tdefault: %s,\\n\\tspecs: %s\\n}\" % (\n            self.default, self.method_list)\n\n\nclass when(object):\n    \"\"\"This annotation lets packages declare multiple versions of\n       methods like install() that depend on the package's spec.\n       For example:\n\n       .. code-block:: python\n\n          class SomePackage(Package):\n              ...\n\n              def install(self, prefix):\n                  # Do default install\n\n              @when('arch=chaos_5_x86_64_ib')\n              def install(self, prefix):\n                  # This will be executed instead of the default install if\n                  # the package's platform() is chaos_5_x86_64_ib.\n\n              @when('arch=bgqos_0\")\n              def install(self, prefix):\n                  # This will be executed if the package's sys_type is bgqos_0\n\n       This allows each package to have a default version of install() AND\n       specialized versions for particular platforms.  The version that is\n       called depends on the architecutre of the instantiated package.\n\n       Note that this works for methods other than install, as well.  So,\n       if you only have part of the install that is platform specific, you\n       could do this:\n\n       .. code-block:: python\n\n          class SomePackage(Package):\n              ...\n              # virtual dependence on MPI.\n              # could resolve to mpich, mpich2, OpenMPI\n              depends_on('mpi')\n\n              def setup(self):\n                  # do nothing in the default case\n                  pass\n\n              @when('^openmpi')\n              def setup(self):\n                  # do something special when this is built with OpenMPI for\n                  # its MPI implementations.\n\n\n              def install(self, prefix):\n                  # Do common install stuff\n                  self.setup()\n                  # Do more common install stuff\n\n       There must be one (and only one) @when clause that matches the\n       package's spec.  If there is more than one, or if none match,\n       then the method will raise an exception when it's called.\n\n       Note that the default version of decorated methods must\n       *always* come first.  Otherwise it will override all of the\n       platform-specific versions.  There's not much we can do to get\n       around this because of the way decorators work.\n    \"\"\"\n\n    def __init__(self, spec):\n        pkg = get_calling_module_name()\n        if spec is True:\n            spec = pkg\n        self.spec = (parse_anonymous_spec(spec, pkg)\n                     if spec is not False else None)\n\n    def __call__(self, method):\n        # Get the first definition of the method in the calling scope\n        original_method = caller_locals().get(method.__name__)\n\n        # Create a multimethod out of the original method if it\n        # isn't one already.\n        if not type(original_method) == SpecMultiMethod:\n            original_method = SpecMultiMethod(original_method)\n\n        if self.spec is not None:\n            original_method.register(self.spec, method)\n\n        return original_method\n\n\nclass MultiMethodError(spack.error.SpackError):\n    \"\"\"Superclass for multimethod dispatch errors\"\"\"\n\n    def __init__(self, message):\n        super(MultiMethodError, self).__init__(message)\n\n\nclass NoSuchMethodError(spack.error.SpackError):\n    \"\"\"Raised when we can't find a version of a multi-method.\"\"\"\n\n    def __init__(self, cls, method_name, spec, possible_specs):\n        super(NoSuchMethodError, self).__init__(\n            \"Package %s does not support %s called with %s.  Options are: %s\"\n            % (cls.__name__, method_name, spec,\n               \", \".join(str(s) for s in possible_specs)))\n", "path": "lib/spack/spack/multimethod.py"}], "after_files": [{"content": "##############################################################################\n# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.\n# Produced at the Lawrence Livermore National Laboratory.\n#\n# This file is part of Spack.\n# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.\n# LLNL-CODE-647188\n#\n# For details, see https://github.com/llnl/spack\n# Please also see the LICENSE file for our notice and the LGPL.\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License (as\n# published by the Free Software Foundation) version 2.1, February 1999.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and\n# conditions of the GNU Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public\n# License along with this program; if not, write to the Free Software\n# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n##############################################################################\n\"\"\"This module contains utilities for using multi-methods in\nspack. You can think of multi-methods like overloaded methods --\nthey're methods with the same name, and we need to select a version\nof the method based on some criteria.  e.g., for overloaded\nmethods, you would select a version of the method to call based on\nthe types of its arguments.\n\nIn spack, multi-methods are used to ease the life of package\nauthors.  They allow methods like install() (or other methods\ncalled by install()) to declare multiple versions to be called when\nthe package is instantiated with different specs.  e.g., if the\npackage is built with OpenMPI on x86_64,, you might want to call a\ndifferent install method than if it was built for mpich2 on\nBlueGene/Q.  Likewise, you might want to do a different type of\ninstall for different versions of the package.\n\nMulti-methods provide a simple decorator-based syntax for this that\navoids overly complicated rat nests of if statements.  Obviously,\ndepending on the scenario, regular old conditionals might be clearer,\nso package authors should use their judgement.\n\"\"\"\nimport functools\n\nfrom llnl.util.lang import *\n\nimport spack.architecture\nimport spack.error\nfrom spack.spec import parse_anonymous_spec\n\n\nclass SpecMultiMethod(object):\n    \"\"\"This implements a multi-method for Spack specs.  Packages are\n       instantiated with a particular spec, and you may want to\n       execute different versions of methods based on what the spec\n       looks like.  For example, you might want to call a different\n       version of install() for one platform than you call on another.\n\n       The SpecMultiMethod class implements a callable object that\n       handles method dispatch.  When it is called, it looks through\n       registered methods and their associated specs, and it tries\n       to find one that matches the package's spec.  If it finds one\n       (and only one), it will call that method.\n\n       The package author is responsible for ensuring that only one\n       condition on multi-methods ever evaluates to true.  If\n       multiple methods evaluate to true, this will raise an\n       exception.\n\n       This is intended for use with decorators (see below).  The\n       decorator (see docs below) creates SpecMultiMethods and\n       registers method versions with them.\n\n       To register a method, you can do something like this:\n           mm = SpecMultiMethod()\n           mm.register(\"^chaos_5_x86_64_ib\", some_method)\n\n       The object registered needs to be a Spec or some string that\n       will parse to be a valid spec.\n\n       When the mm is actually called, it selects a version of the\n       method to call based on the sys_type of the object it is\n       called on.\n\n       See the docs for decorators below for more details.\n    \"\"\"\n\n    def __init__(self, default=None):\n        self.method_list = []\n        self.default = default\n        if default:\n            functools.update_wrapper(self, default)\n\n    def register(self, spec, method):\n        \"\"\"Register a version of a method for a particular sys_type.\"\"\"\n        self.method_list.append((spec, method))\n\n        if not hasattr(self, '__name__'):\n            functools.update_wrapper(self, method)\n        else:\n            assert(self.__name__ == method.__name__)\n\n    def __get__(self, obj, objtype):\n        \"\"\"This makes __call__ support instance methods.\"\"\"\n        # Method_list is a list of tuples (constraint, method)\n        # Here we are going to assume that we have at least one\n        # element in the list. The first registered function\n        # will be the one 'wrapped'.\n        wrapped_method = self.method_list[0][1]\n        # Call functools.wraps manually to get all the attributes\n        # we need to be disguised as the wrapped_method\n        func = functools.wraps(wrapped_method)(\n            functools.partial(self.__call__, obj)\n        )\n        return func\n\n    def __call__(self, package_self, *args, **kwargs):\n        \"\"\"Find the first method with a spec that matches the\n           package's spec.  If none is found, call the default\n           or if there is none, then raise a NoSuchMethodError.\n        \"\"\"\n        for spec, method in self.method_list:\n            if package_self.spec.satisfies(spec):\n                return method(package_self, *args, **kwargs)\n\n        if self.default:\n            return self.default(package_self, *args, **kwargs)\n        else:\n            raise NoSuchMethodError(\n                type(package_self), self.__name__, spec,\n                [m[0] for m in self.method_list])\n\n    def __str__(self):\n        return \"SpecMultiMethod {\\n\\tdefault: %s,\\n\\tspecs: %s\\n}\" % (\n            self.default, self.method_list)\n\n\nclass when(object):\n    \"\"\"This annotation lets packages declare multiple versions of\n       methods like install() that depend on the package's spec.\n       For example:\n\n       .. code-block:: python\n\n          class SomePackage(Package):\n              ...\n\n              def install(self, prefix):\n                  # Do default install\n\n              @when('arch=chaos_5_x86_64_ib')\n              def install(self, prefix):\n                  # This will be executed instead of the default install if\n                  # the package's platform() is chaos_5_x86_64_ib.\n\n              @when('arch=bgqos_0\")\n              def install(self, prefix):\n                  # This will be executed if the package's sys_type is bgqos_0\n\n       This allows each package to have a default version of install() AND\n       specialized versions for particular platforms.  The version that is\n       called depends on the architecutre of the instantiated package.\n\n       Note that this works for methods other than install, as well.  So,\n       if you only have part of the install that is platform specific, you\n       could do this:\n\n       .. code-block:: python\n\n          class SomePackage(Package):\n              ...\n              # virtual dependence on MPI.\n              # could resolve to mpich, mpich2, OpenMPI\n              depends_on('mpi')\n\n              def setup(self):\n                  # do nothing in the default case\n                  pass\n\n              @when('^openmpi')\n              def setup(self):\n                  # do something special when this is built with OpenMPI for\n                  # its MPI implementations.\n\n\n              def install(self, prefix):\n                  # Do common install stuff\n                  self.setup()\n                  # Do more common install stuff\n\n       There must be one (and only one) @when clause that matches the\n       package's spec.  If there is more than one, or if none match,\n       then the method will raise an exception when it's called.\n\n       Note that the default version of decorated methods must\n       *always* come first.  Otherwise it will override all of the\n       platform-specific versions.  There's not much we can do to get\n       around this because of the way decorators work.\n    \"\"\"\n\n    def __init__(self, spec):\n        pkg = get_calling_module_name()\n        if spec is True:\n            spec = pkg\n        self.spec = (parse_anonymous_spec(spec, pkg)\n                     if spec is not False else None)\n\n    def __call__(self, method):\n        # Get the first definition of the method in the calling scope\n        original_method = caller_locals().get(method.__name__)\n\n        # Create a multimethod out of the original method if it\n        # isn't one already.\n        if not type(original_method) == SpecMultiMethod:\n            original_method = SpecMultiMethod(original_method)\n\n        if self.spec is not None:\n            original_method.register(self.spec, method)\n\n        return original_method\n\n\nclass MultiMethodError(spack.error.SpackError):\n    \"\"\"Superclass for multimethod dispatch errors\"\"\"\n\n    def __init__(self, message):\n        super(MultiMethodError, self).__init__(message)\n\n\nclass NoSuchMethodError(spack.error.SpackError):\n    \"\"\"Raised when we can't find a version of a multi-method.\"\"\"\n\n    def __init__(self, cls, method_name, spec, possible_specs):\n        super(NoSuchMethodError, self).__init__(\n            \"Package %s does not support %s called with %s.  Options are: %s\"\n            % (cls.__name__, method_name, spec,\n               \", \".join(str(s) for s in possible_specs)))\n", "path": "lib/spack/spack/multimethod.py"}]}