From 5c7a84b74780e5d4ed45b1a7271355eb144c32fc Mon Sep 17 00:00:00 2001
From: Gauthier Folzan <gauthier@wuhu.eu>
Date: Sat, 16 May 2020 01:36:48 +0200
Subject: [PATCH] Using a unique multi-valued variant for defining the modules:

CON :
- Users have to give explicitly the list of required modules but the
  error message gives all the modules to add

PRO :
 - Does not hide automatic installed dependecies
   (with the one module - one variant, a module can be installed and the corresponding variant set to false)
 - Module dependencies is done when checking the arguments
 - Does not tamper with spack resource fetching
---
 packages/dune/package.py | 143 ++++++++++++++++++++++-----------------
 1 file changed, 81 insertions(+), 62 deletions(-)

diff --git a/packages/dune/package.py b/packages/dune/package.py
index c172af9..1b4f439 100644
--- a/packages/dune/package.py
+++ b/packages/dune/package.py
@@ -22,7 +22,11 @@
 
 import os
 from spack import *
-
+try:
+    from collections.abc import Sequence
+except ImportError:
+    from collections import Sequence
+import spack.error as error
 
 class Dune(CMakePackage):
     """
@@ -64,17 +68,72 @@ class Dune(CMakePackage):
     # Some variants that were here that are on my todo list
     # variant('jupyter', default=False, description='Build with Jupyter support')
 
-    # Define one variant for each Dune module that we have. Only core modules
-    # are activated by default.
-    variant('alugrid', default=False, description='Build with dune-alugrid module')
-    variant('functions', default=False, description='Build with dune-functions module')
-    variant('geometry', default=True, description='Build with dune-geometry module')
-    variant('grid', default=True, description='Build with dune-grid module')
-    variant('istl', default=True, description='Build with dune-istl module')
-    variant('localfunctions', default=True, description='Build with dune-localfunctions module')
-    variant('spgrid', default=False, description='Build with dune-spgrid module')
-    variant('typetree', default=False, description='Build with dune-typetree module')
-    variant('uggrid', default=False, description='Build with dune-uggrid module')
+    # Define a multi-valued variant for defining the modules
+
+    class dune_module_set(Sequence):
+        """Define the possible values for the DUNE modules variants"""
+        def __init__(self):
+            self.module_dependencies={}
+            self.module_dependencies["common"]=[]
+            self.module_dependencies["geometry"]=["common"]
+            self.module_dependencies["grid"]=["common","geometry"]
+            self.module_dependencies["uggrid"]=["common"]
+            self.module_dependencies["istl"]=["common"]
+            self.module_dependencies["localfunctions"]=["common","geometry"]
+            self.module_dependencies["functions"]=["grid","typetree","localfunctions","istl"]
+            self.module_dependencies["typetree"]=["common"]
+            self.module_dependencies["python"]=[]
+            self.module_dependencies["spgrid"]=[]
+            self.module_dependencies["alugrid"]=["grid","geometry","common"]
+            self.sets=[]
+            for key in self.module_dependencies:
+                self.sets.append(key)
+            self.feature_values = tuple(self.sets)
+            self.default = ("common","grid")
+            self.multi = True
+
+        def __getitem__(self, idx):
+            return self.feature_values[idx]
+
+        def __len__(self):
+            return len(self.feature_values)
+
+        @property
+        def validator(self):
+            def module_dependency_closure(modlist):
+                closure = set(modlist)
+                old_closure = set()
+                while (len(closure) > len(old_closure)):
+                    old_closure = closure.copy()
+                    for res in old_closure:
+                        for mod in self.module_dependencies.get(res):
+                            closure.add(mod)
+                return closure
+            def _dune_modules_set_validator(pkg_name, variant_name, values):
+                ok=True
+                msg='Missing module dependencies: '
+                for s in values:
+                    req_mod=module_dependency_closure({s})
+                    for req in req_mod:
+                        if(not req in values):
+                            msg+=req+','
+                            ok=False
+                if(ok):
+                    return
+                msg = msg[:-1]
+                format_args = {
+                    'variant': variant_name, 'package': pkg_name, 'values': values
+                }
+                msg = msg + \
+                    " @*r{{[{package}, variant '{variant}']}}"
+                msg = llnl.util.tty.color.colorize(msg.format(**format_args))
+                raise error.SpecError(msg)
+            return _dune_modules_set_validator
+
+    variant( 'modules', 
+        values=dune_module_set(),
+        description='Installed dune module')
+
 
     # Iterate over all available Dune versions and define resources for all Dune modules
     # If a Dune module behaves differently for different versions (e.g. dune-python got
@@ -86,84 +145,72 @@ class Dune(CMakePackage):
             name='dune-geometry',
             git='https://gitlab.dune-project.org/core/dune-geometry.git',
             branch=branch,
-            when='@%s+geometry' % vers,
+            when='@%s modules=geometry' % vers,
         )
 
         resource(
             name='dune-grid',
             git='https://gitlab.dune-project.org/core/dune-grid.git',
             branch=branch,
-            when='@%s+grid' % vers,
+            when='@%s modules=grid' % vers,
         )
 
         resource(
             name='dune-istl',
             git='https://gitlab.dune-project.org/core/dune-istl.git',
             branch=branch,
-            when='@%s+istl' % vers,
+            when='@%s modules=istl' % vers,
         )
 
         resource(
             name='dune-localfunctions',
             git='https://gitlab.dune-project.org/core/dune-localfunctions.git',
             branch=branch,
-            when='@%s+localfunctions' % vers,
+            when='@%s modules=localfunctions' % vers,
         )
 
         resource(
             name='dune-functions',
             git='https://gitlab.dune-project.org/staging/dune-functions.git',
             branch=branch,
-            when='@%s+functions' % vers,
+            when='@%s modules=functions' % vers,
         )
 
         resource(
             name='dune-typetree',
             git='https://gitlab.dune-project.org/staging/dune-typetree.git',
             branch=branch,
-            when='@%s+typetree' % vers,
+            when='@%s modules=typetree' % vers,
         )
 
         resource(
             name='dune-alugrid',
             git='https://gitlab.dune-project.org/extensions/dune-alugrid.git',
             branch=branch,
-            when='@%s+alugrid' % vers,
+            when='@%s modules=alugrid' % vers,
         )
 
         resource(
             name='dune-uggrid',
             git='https://gitlab.dune-project.org/staging/dune-uggrid.git',
             branch='releases/2.7',
-            when='@%s+uggrid' % vers,
+            when='@%s modules=uggrid' % vers,
         )
 
         resource(
             name='dune-spgrid',
             git='https://gitlab.dune-project.org/extensions/dune-spgrid.git',
             branch=branch,
-            when='@%s+spgrid' % vers,
+            when='@%s modules=spgrid' % vers,
         )
 
     resource(
         name='dune-python',
         git='https://gitlab.dune-project.org/staging/dune-python.git',
         branch=branch,
-        when='@2.7+python',
+        when='@2.7 modules=python',
     )
 
-    # Dependencies between modules
-    module_dependencies={"dune-common":[]}
-    module_dependencies["dune-geometry"]=["dune-common"]
-    module_dependencies["dune-grid"]=["dune-common","dune-geometry"]
-    module_dependencies["dune-uggrid"]=["dune-common"]
-    module_dependencies["dune-istl"]=["dune-common"]
-    module_dependencies["dune-localfunctions"]=["dune-common","dune-geometry"]
-    module_dependencies["dune-functions"]=["dune-grid","dune-typetree","dune-localfunctions","dune-istl"]
-    module_dependencies["dune-typetree"]=["dune-common"]
-    module_dependencies["dune-python"]=[]
-    module_dependencies["dune-alugrid"]=["dune-grid","dune-geometry","dune-common"]
-
     extends('python')
     python_components = [ 'dune' ]
 
@@ -251,34 +298,6 @@ class Dune(CMakePackage):
 
         return cmake_args
 
-    def module_dependency_closure(self, when, reslist):
-        # Get a list of all modules that share the version requirement
-        all_resources = []
-        for w, rl in self.resources.items():
-            if w.version == when.version:
-                all_resources.extend(rl)
-
-        # And build the closure of modules from the given reslist
-        closure = set(reslist)
-        old_closure = set()
-        while (len(closure) > len(old_closure)):
-            old_closure = closure.copy()
-
-            for res in old_closure:
-                for mod in self.module_dependencies.get(res.name, []):
-                    for r in all_resources:
-                        if r.name == mod:
-                            closure.add(r)
-
-        return list(closure)
-
-    def _get_needed_resources(self):
-        # Modify the list of resources by building a transitive closure of Dune module dependencies.
-        self.resources = {when: self.module_dependency_closure(when, reslist) for when, reslist in self.resources.items()}
-
-        # and call the original implementation
-        return CMakePackage._get_needed_resources(self)
-
     def cmake(self, spec, prefix):
         # Write an opts file for later use
         with open(join_path(self.stage.source_path, "..", "dune.opts"), "w") as optFile: