unparser: handle package-level loops, if statements, and with blocks
Many packages implement logic at the class level to handle complex dependencies and
conflicts. Others have started using `with when("@1.0"):` blocks since we added that
capability. The loops and other control logic can cause some pure directive logic not to
be removed by our package hashing logic -- and in many cases that's a lot of code that
will cause unnecessary rebuilds.
This commit changes the unparser so that it will descend into these blocks. Specifically:
  1. Descend into loops, if statements, and with blocks at the class level.
  2. Don't look inside function definitions (in or outside a class).
  3. Don't look at nested class definitions (they don't have directives)
  4. Add logic to *remove* empty loops/with blocks/if statements if all directives
     in them were removed.
This allows our package hash to ignore a lot of pure metadata that it was not ignoring
before, and makes it less sensitive.
In addition, we add `maintainers` and `tags` to the list of metadata attributes that
Spack should remove from packages when constructing canonoical source for a package
hash.
- [x] Make unparser handle if/for/while/with at class level.
- [x] Add tests for control logic removal.
- [x] Add a test to ensure that all packages are not only unparseable, but also
      that their canonical source is still compilable. This is a test for
      our control logic removal.
- [x] Add another unparse test package that has complex logic.
			
			
This commit is contained in:
		 Todd Gamblin
					Todd Gamblin
				
			
				
					committed by
					
						 Greg Becker
						Greg Becker
					
				
			
			
				
	
			
			
			 Greg Becker
						Greg Becker
					
				
			
						parent
						
							101f080138
						
					
				
				
					commit
					54d741ba54
				
			| @@ -676,8 +676,17 @@ class PackageBase(six.with_metaclass(PackageMeta, PackageViewMixin, object)): | ||||
|     maintainers = []  # type: List[str] | ||||
| 
 | ||||
|     #: List of attributes to be excluded from a package's hash. | ||||
|     metadata_attrs = ['homepage', 'url', 'urls', 'list_url', 'extendable', | ||||
|                       'parallel', 'make_jobs'] | ||||
|     metadata_attrs = [ | ||||
|         "homepage", | ||||
|         "url", | ||||
|         "urls", | ||||
|         "list_url", | ||||
|         "extendable", | ||||
|         "parallel", | ||||
|         "make_jobs", | ||||
|         "maintainers", | ||||
|         "tags", | ||||
|     ] | ||||
| 
 | ||||
|     #: Boolean. If set to ``True``, the smoke/install test requires a compiler. | ||||
|     #: This is currently used by smoke tests to ensure a compiler is available | ||||
|   | ||||
							
								
								
									
										786
									
								
								lib/spack/spack/test/data/unparse/trilinos.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										786
									
								
								lib/spack/spack/test/data/unparse/trilinos.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,786 @@ | ||||
|  # -*- python -*- | ||||
| # Copyright 2013-2021 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) | ||||
| """This is an unparser test package. | ||||
|  | ||||
| ``trilinos`` was chosen because it's one of the most complex packages in Spack, because | ||||
| it has a lot of nested  ``with when():`` blocks, and because it has loops and nested | ||||
| logic at the package level. | ||||
|  | ||||
| """ | ||||
|  | ||||
| import os | ||||
| import sys | ||||
|  | ||||
| from spack import * | ||||
| from spack.build_environment import dso_suffix | ||||
| from spack.error import NoHeadersError | ||||
| from spack.operating_systems.mac_os import macos_version | ||||
| from spack.pkg.builtin.kokkos import Kokkos | ||||
|  | ||||
| # Trilinos is complicated to build, as an inspiration a couple of links to | ||||
| # other repositories which build it: | ||||
| # https://github.com/hpcugent/easybuild-easyblocks/blob/master/easybuild/easyblocks/t/trilinos.py#L111 | ||||
| # https://github.com/koecher/candi/blob/master/deal.II-toolchain/packages/trilinos.package | ||||
| # https://gitlab.com/configurations/cluster-config/blob/master/trilinos.sh | ||||
| # https://github.com/Homebrew/homebrew-science/blob/master/trilinos.rb and some | ||||
| # relevant documentation/examples: | ||||
| # https://github.com/trilinos/Trilinos/issues/175 | ||||
|  | ||||
|  | ||||
| class Trilinos(CMakePackage, CudaPackage): | ||||
|     """The Trilinos Project is an effort to develop algorithms and enabling | ||||
|     technologies within an object-oriented software framework for the solution | ||||
|     of large-scale, complex multi-physics engineering and scientific problems. | ||||
|     A unique design feature of Trilinos is its focus on packages. | ||||
|     """ | ||||
|     homepage = "https://trilinos.org/" | ||||
|     url      = "https://github.com/trilinos/Trilinos/archive/trilinos-release-12-12-1.tar.gz" | ||||
|     git      = "https://github.com/trilinos/Trilinos.git" | ||||
|  | ||||
|     maintainers = ['keitat', 'sethrj', 'kuberry'] | ||||
|  | ||||
|     tags = ['e4s'] | ||||
|  | ||||
|     # ###################### Versions ########################## | ||||
|  | ||||
|     version('master', branch='master') | ||||
|     version('develop', branch='develop') | ||||
|     version('13.2.0', commit='4a5f7906a6420ee2f9450367e9cc95b28c00d744')  # tag trilinos-release-13-2-0 | ||||
|     version('13.0.1', commit='4796b92fb0644ba8c531dd9953e7a4878b05c62d', preferred=True)  # tag trilinos-release-13-0-1 | ||||
|     version('13.0.0', commit='9fec35276d846a667bc668ff4cbdfd8be0dfea08')  # tag trilinos-release-13-0-0 | ||||
|     version('12.18.1', commit='55a75997332636a28afc9db1aee4ae46fe8d93e7')  # tag trilinos-release-12-8-1 | ||||
|     version('12.14.1', sha256='52a4406cca2241f5eea8e166c2950471dd9478ad6741cbb2a7fc8225814616f0') | ||||
|     version('12.12.1', sha256='5474c5329c6309224a7e1726cf6f0d855025b2042959e4e2be2748bd6bb49e18') | ||||
|     version('12.10.1', sha256='ab81d917196ffbc21c4927d42df079dd94c83c1a08bda43fef2dd34d0c1a5512') | ||||
|     version('12.8.1', sha256='d20fe60e31e3ba1ef36edecd88226240a518f50a4d6edcc195b88ee9dda5b4a1') | ||||
|     version('12.6.4', sha256='1c7104ba60ee8cc4ec0458a1c4f6a26130616bae7580a7b15f2771a955818b73') | ||||
|     version('12.6.3', sha256='4d28298bb4074eef522db6cd1626f1a934e3d80f292caf669b8846c0a458fe81') | ||||
|     version('12.6.2', sha256='8be7e3e1166cc05aea7f856cc8033182e8114aeb8f87184cb38873bfb2061779') | ||||
|     version('12.6.1', sha256='4b38ede471bed0036dcb81a116fba8194f7bf1a9330da4e29c3eb507d2db18db') | ||||
|     version('12.4.2', sha256='fd2c12e87a7cedc058bcb8357107ffa2474997aa7b17b8e37225a1f7c32e6f0e') | ||||
|     version('12.2.1', sha256='088f303e0dc00fb4072b895c6ecb4e2a3ad9a2687b9c62153de05832cf242098') | ||||
|     version('12.0.1', sha256='eee7c19ca108538fa1c77a6651b084e06f59d7c3307dae77144136639ab55980') | ||||
|     version('11.14.3', sha256='e37fa5f69103576c89300e14d43ba77ad75998a54731008b25890d39892e6e60') | ||||
|     version('11.14.2', sha256='f22b2b0df7b88e28b992e19044ba72b845292b93cbbb3a948488199647381119') | ||||
|     version('11.14.1', sha256='f10fc0a496bf49427eb6871c80816d6e26822a39177d850cc62cf1484e4eec07') | ||||
|  | ||||
|     # ###################### Variants ########################## | ||||
|  | ||||
|     # Build options | ||||
|     variant('complex', default=False, description='Enable complex numbers in Trilinos') | ||||
|     variant('cuda_rdc', default=False, description='turn on RDC for CUDA build') | ||||
|     variant('cxxstd', default='14', values=['11', '14', '17'], multi=False) | ||||
|     variant('debug', default=False, description='Enable runtime safety and debug checks') | ||||
|     variant('explicit_template_instantiation', default=True, description='Enable explicit template instantiation (ETI)') | ||||
|     variant('float', default=False, description='Enable single precision (float) numbers in Trilinos') | ||||
|     variant('fortran', default=True, description='Compile with Fortran support') | ||||
|     variant('gotype', default='long_long', | ||||
|             values=('int', 'long', 'long_long', 'all'), | ||||
|             multi=False, | ||||
|             description='global ordinal type for Tpetra') | ||||
|     variant('openmp', default=False, description='Enable OpenMP') | ||||
|     variant('python', default=False, description='Build PyTrilinos wrappers') | ||||
|     variant('shared', default=True, description='Enables the build of shared libraries') | ||||
|     variant('wrapper', default=False, description="Use nvcc-wrapper for CUDA build") | ||||
|  | ||||
|     # TPLs (alphabet order) | ||||
|     variant('adios2',       default=False, description='Enable ADIOS2') | ||||
|     variant('boost',        default=False, description='Compile with Boost') | ||||
|     variant('hdf5',         default=False, description='Compile with HDF5') | ||||
|     variant('hypre',        default=False, description='Compile with Hypre preconditioner') | ||||
|     variant('mpi',          default=True, description='Compile with MPI parallelism') | ||||
|     variant('mumps',        default=False, description='Compile with support for MUMPS solvers') | ||||
|     variant('suite-sparse', default=False, description='Compile with SuiteSparse solvers') | ||||
|     variant('superlu-dist', default=False, description='Compile with SuperluDist solvers') | ||||
|     variant('superlu',      default=False, description='Compile with SuperLU solvers') | ||||
|     variant('strumpack',    default=False, description='Compile with STRUMPACK solvers') | ||||
|     variant('x11',          default=False, description='Compile with X11 when +exodus') | ||||
|  | ||||
|     # Package options (alphabet order) | ||||
|     variant('amesos',       default=True, description='Compile with Amesos') | ||||
|     variant('amesos2',      default=True, description='Compile with Amesos2') | ||||
|     variant('anasazi',      default=True, description='Compile with Anasazi') | ||||
|     variant('aztec',        default=True, description='Compile with Aztec') | ||||
|     variant('belos',        default=True, description='Compile with Belos') | ||||
|     variant('chaco',        default=False, description='Compile with Chaco from SEACAS') | ||||
|     variant('epetra',       default=True, description='Compile with Epetra') | ||||
|     variant('epetraext',    default=True, description='Compile with EpetraExt') | ||||
|     variant('exodus',       default=False, description='Compile with Exodus from SEACAS') | ||||
|     variant('ifpack',       default=True, description='Compile with Ifpack') | ||||
|     variant('ifpack2',      default=True, description='Compile with Ifpack2') | ||||
|     variant('intrepid',     default=False, description='Enable Intrepid') | ||||
|     variant('intrepid2',    default=False, description='Enable Intrepid2') | ||||
|     variant('isorropia',    default=False, description='Compile with Isorropia') | ||||
|     variant('gtest',        default=False, description='Build vendored Googletest') | ||||
|     variant('kokkos',       default=True, description='Compile with Kokkos') | ||||
|     variant('ml',           default=True, description='Compile with ML') | ||||
|     variant('minitensor',   default=False, description='Compile with MiniTensor') | ||||
|     variant('muelu',        default=True, description='Compile with Muelu') | ||||
|     variant('nox',          default=False, description='Compile with NOX') | ||||
|     variant('piro',         default=False, description='Compile with Piro') | ||||
|     variant('phalanx',      default=False, description='Compile with Phalanx') | ||||
|     variant('rol',          default=False, description='Compile with ROL') | ||||
|     variant('rythmos',      default=False, description='Compile with Rythmos') | ||||
|     variant('sacado',       default=True, description='Compile with Sacado') | ||||
|     variant('stk',          default=False, description='Compile with STK') | ||||
|     variant('shards',       default=False, description='Compile with Shards') | ||||
|     variant('shylu',        default=False, description='Compile with ShyLU') | ||||
|     variant('stokhos',      default=False, description='Compile with Stokhos') | ||||
|     variant('stratimikos',  default=False, description='Compile with Stratimikos') | ||||
|     variant('teko',         default=False, description='Compile with Teko') | ||||
|     variant('tempus',       default=False, description='Compile with Tempus') | ||||
|     variant('tpetra',       default=True, description='Compile with Tpetra') | ||||
|     variant('trilinoscouplings', default=False, description='Compile with TrilinosCouplings') | ||||
|     variant('zoltan',       default=False, description='Compile with Zoltan') | ||||
|     variant('zoltan2',      default=False, description='Compile with Zoltan2') | ||||
|  | ||||
|     # Internal package options (alphabetical order) | ||||
|     variant('basker',                    default=False, description='Compile with the Basker solver in Amesos2') | ||||
|     variant('epetraextbtf',              default=False, description='Compile with BTF in EpetraExt') | ||||
|     variant('epetraextexperimental',     default=False, description='Compile with experimental in EpetraExt') | ||||
|     variant('epetraextgraphreorderings', default=False, description='Compile with graph reorderings in EpetraExt') | ||||
|  | ||||
|     # External package options | ||||
|     variant('dtk',      default=False, description='Enable DataTransferKit (deprecated)') | ||||
|     variant('scorec',   default=False, description='Enable SCOREC') | ||||
|     variant('mesquite', default=False, description='Enable Mesquite (deprecated)') | ||||
|  | ||||
|     resource(name='dtk', | ||||
|              git='https://github.com/ornl-cees/DataTransferKit.git', | ||||
|              commit='4fe4d9d56cfd4f8a61f392b81d8efd0e389ee764',  # branch dtk-3.0 | ||||
|              placement='DataTransferKit', | ||||
|              when='+dtk @12.14.0:12.14') | ||||
|     resource(name='dtk', | ||||
|              git='https://github.com/ornl-cees/DataTransferKit.git', | ||||
|              commit='edfa050cd46e2274ab0a0b7558caca0079c2e4ca',  # tag 3.1-rc1 | ||||
|              placement='DataTransferKit', | ||||
|              submodules=True, | ||||
|              when='+dtk @12.18.0:12.18') | ||||
|     resource(name='scorec', | ||||
|              git='https://github.com/SCOREC/core.git', | ||||
|              commit='73c16eae073b179e45ec625a5abe4915bc589af2',  # tag v2.2.5 | ||||
|              placement='SCOREC', | ||||
|              when='+scorec') | ||||
|     resource(name='mesquite', | ||||
|              url='https://github.com/trilinos/mesquite/archive/trilinos-release-12-12-1.tar.gz', | ||||
|              sha256='e0d09b0939dbd461822477449dca611417316e8e8d8268fd795debb068edcbb5', | ||||
|              placement='packages/mesquite', | ||||
|              when='+mesquite @12.12.1:12.16') | ||||
|     resource(name='mesquite', | ||||
|              git='https://github.com/trilinos/mesquite.git', | ||||
|              commit='20a679679b5cdf15bf573d66c5dc2b016e8b9ca1',  # branch trilinos-release-12-12-1 | ||||
|              placement='packages/mesquite', | ||||
|              when='+mesquite @12.18.1:12.18') | ||||
|     resource(name='mesquite', | ||||
|              git='https://github.com/trilinos/mesquite.git', | ||||
|              tag='develop', | ||||
|              placement='packages/mesquite', | ||||
|              when='+mesquite @master') | ||||
|  | ||||
|     # ###################### Conflicts ########################## | ||||
|  | ||||
|     # Epetra packages | ||||
|     with when('~epetra'): | ||||
|         conflicts('+amesos') | ||||
|         conflicts('+aztec') | ||||
|         conflicts('+epetraext') | ||||
|         conflicts('+ifpack') | ||||
|         conflicts('+isorropia') | ||||
|         conflicts('+ml', when='@13.2:') | ||||
|     with when('~epetraext'): | ||||
|         conflicts('+isorropia') | ||||
|         conflicts('+teko') | ||||
|         conflicts('+epetraextbtf') | ||||
|         conflicts('+epetraextexperimental') | ||||
|         conflicts('+epetraextgraphreorderings') | ||||
|  | ||||
|     # Tpetra packages | ||||
|     with when('~kokkos'): | ||||
|         conflicts('+cuda') | ||||
|         conflicts('+tpetra') | ||||
|         conflicts('+intrepid2') | ||||
|         conflicts('+phalanx') | ||||
|     with when('~tpetra'): | ||||
|         conflicts('+amesos2') | ||||
|         conflicts('+dtk') | ||||
|         conflicts('+ifpack2') | ||||
|         conflicts('+muelu') | ||||
|         conflicts('+teko') | ||||
|         conflicts('+zoltan2') | ||||
|  | ||||
|     with when('+teko'): | ||||
|         conflicts('~amesos') | ||||
|         conflicts('~anasazi') | ||||
|         conflicts('~aztec') | ||||
|         conflicts('~ifpack') | ||||
|         conflicts('~ml') | ||||
|         conflicts('~stratimikos') | ||||
|         conflicts('@:12 gotype=long') | ||||
|  | ||||
|     # Known requirements from tribits dependencies | ||||
|     conflicts('+aztec', when='~fortran') | ||||
|     conflicts('+basker', when='~amesos2') | ||||
|     conflicts('+minitensor', when='~boost') | ||||
|     conflicts('+ifpack2', when='~belos') | ||||
|     conflicts('+intrepid', when='~sacado') | ||||
|     conflicts('+intrepid', when='~shards') | ||||
|     conflicts('+intrepid2', when='~shards') | ||||
|     conflicts('+isorropia', when='~zoltan') | ||||
|     conflicts('+phalanx', when='~sacado') | ||||
|     conflicts('+scorec', when='~mpi') | ||||
|     conflicts('+scorec', when='~shards') | ||||
|     conflicts('+scorec', when='~stk') | ||||
|     conflicts('+scorec', when='~zoltan') | ||||
|     conflicts('+tempus', when='~nox') | ||||
|     conflicts('+zoltan2', when='~zoltan') | ||||
|  | ||||
|     # Only allow DTK with Trilinos 12.14, 12.18 | ||||
|     conflicts('+dtk', when='~boost') | ||||
|     conflicts('+dtk', when='~intrepid2') | ||||
|     conflicts('+dtk', when='@:12.12,13:') | ||||
|  | ||||
|     # Installed FindTrilinos are broken in SEACAS if Fortran is disabled | ||||
|     # see https://github.com/trilinos/Trilinos/issues/3346 | ||||
|     conflicts('+exodus', when='@:13.0.1 ~fortran') | ||||
|     # Only allow Mesquite with Trilinos 12.12 and up, and master | ||||
|     conflicts('+mesquite', when='@:12.10,master') | ||||
|     # Strumpack is only available as of mid-2021 | ||||
|     conflicts('+strumpack', when='@:13.0') | ||||
|     # Can only use one type of SuperLU | ||||
|     conflicts('+superlu-dist', when='+superlu') | ||||
|     # For Trilinos v11 we need to force SuperLUDist=OFF, since only the | ||||
|     # deprecated SuperLUDist v3.3 together with an Amesos patch is working. | ||||
|     conflicts('+superlu-dist', when='@11.4.1:11.14.3') | ||||
|     # see https://github.com/trilinos/Trilinos/issues/3566 | ||||
|     conflicts('+superlu-dist', when='+float+amesos2+explicit_template_instantiation^superlu-dist@5.3.0:') | ||||
|     # Amesos, conflicting types of double and complex SLU_D | ||||
|     # see https://trilinos.org/pipermail/trilinos-users/2015-March/004731.html | ||||
|     # and https://trilinos.org/pipermail/trilinos-users/2015-March/004802.html | ||||
|     conflicts('+superlu-dist', when='+complex+amesos2') | ||||
|     # https://github.com/trilinos/Trilinos/issues/2994 | ||||
|     conflicts( | ||||
|         '+shared', when='+stk platform=darwin', | ||||
|         msg='Cannot build Trilinos with STK as a shared library on Darwin.' | ||||
|     ) | ||||
|     conflicts('+adios2', when='@:12.14.1') | ||||
|     conflicts('cxxstd=11', when='@master:') | ||||
|     conflicts('cxxstd=11', when='+wrapper ^cuda@6.5.14') | ||||
|     conflicts('cxxstd=14', when='+wrapper ^cuda@6.5.14:8.0.61') | ||||
|     conflicts('cxxstd=17', when='+wrapper ^cuda@6.5.14:10.2.89') | ||||
|  | ||||
|     # Multi-value gotype only applies to trilinos through 12.14 | ||||
|     conflicts('gotype=all', when='@12.15:') | ||||
|  | ||||
|     # CUDA without wrapper requires clang | ||||
|     for _compiler in spack.compilers.supported_compilers(): | ||||
|         if _compiler != 'clang': | ||||
|             conflicts('+cuda', when='~wrapper %' + _compiler, | ||||
|                       msg='trilinos~wrapper+cuda can only be built with the ' | ||||
|                       'Clang compiler') | ||||
|     conflicts('+cuda_rdc', when='~cuda') | ||||
|     conflicts('+wrapper', when='~cuda') | ||||
|     conflicts('+wrapper', when='%clang') | ||||
|  | ||||
|     # Old trilinos fails with new CUDA (see #27180) | ||||
|     conflicts('@:13.0.1 +cuda', when='^cuda@11:') | ||||
|  | ||||
|     # stokhos fails on xl/xl_r | ||||
|     conflicts('+stokhos', when='%xl') | ||||
|     conflicts('+stokhos', when='%xl_r') | ||||
|  | ||||
|     # Fortran mangling fails on Apple M1 (see spack/spack#25900) | ||||
|     conflicts('@:13.0.1 +fortran', when='target=m1') | ||||
|  | ||||
|     # ###################### Dependencies ########################## | ||||
|  | ||||
|     depends_on('adios2', when='+adios2') | ||||
|     depends_on('blas') | ||||
|     depends_on('boost', when='+boost') | ||||
|     depends_on('cgns', when='+exodus') | ||||
|     depends_on('hdf5+hl', when='+hdf5') | ||||
|     depends_on('hypre~internal-superlu~int64', when='+hypre') | ||||
|     depends_on('kokkos-nvcc-wrapper', when='+wrapper') | ||||
|     depends_on('lapack') | ||||
|     # depends_on('perl', type=('build',)) # TriBITS finds but doesn't use... | ||||
|     depends_on('libx11', when='+x11') | ||||
|     depends_on('matio', when='+exodus') | ||||
|     depends_on('metis', when='+zoltan') | ||||
|     depends_on('mpi', when='+mpi') | ||||
|     depends_on('netcdf-c', when="+exodus") | ||||
|     depends_on('parallel-netcdf', when='+exodus+mpi') | ||||
|     depends_on('parmetis', when='+mpi +zoltan') | ||||
|     depends_on('parmetis', when='+scorec') | ||||
|     depends_on('py-mpi4py', when='+mpi+python', type=('build', 'run')) | ||||
|     depends_on('py-numpy', when='+python', type=('build', 'run')) | ||||
|     depends_on('python', when='+python') | ||||
|     depends_on('python', when='@13.2: +ifpack +hypre', type='build') | ||||
|     depends_on('python', when='@13.2: +ifpack2 +hypre', type='build') | ||||
|     depends_on('scalapack', when='+mumps') | ||||
|     depends_on('scalapack', when='+strumpack+mpi') | ||||
|     depends_on('strumpack+shared', when='+strumpack') | ||||
|     depends_on('suite-sparse', when='+suite-sparse') | ||||
|     depends_on('superlu-dist', when='+superlu-dist') | ||||
|     depends_on('superlu@4.3 +pic', when='+superlu') | ||||
|     depends_on('swig', when='+python') | ||||
|     depends_on('zlib', when='+zoltan') | ||||
|  | ||||
|     # Trilinos' Tribits config system is limited which makes it very tricky to | ||||
|     # link Amesos with static MUMPS, see | ||||
|     # https://trilinos.org/docs/dev/packages/amesos2/doc/html/classAmesos2_1_1MUMPS.html | ||||
|     # One could work it out by getting linking flags from mpif90 --showme:link | ||||
|     # (or alike) and adding results to -DTrilinos_EXTRA_LINK_FLAGS together | ||||
|     # with Blas and Lapack and ScaLAPACK and Blacs and -lgfortran and it may | ||||
|     # work at the end. But let's avoid all this by simply using shared libs | ||||
|     depends_on('mumps@5.0:+shared', when='+mumps') | ||||
|  | ||||
|     for _flag in ('~mpi', '+mpi'): | ||||
|         depends_on('hdf5' + _flag, when='+hdf5' + _flag) | ||||
|         depends_on('mumps' + _flag, when='+mumps' + _flag) | ||||
|     for _flag in ('~openmp', '+openmp'): | ||||
|         depends_on('mumps' + _flag, when='+mumps' + _flag) | ||||
|  | ||||
|     depends_on('hwloc', when='@13: +kokkos') | ||||
|     depends_on('hwloc+cuda', when='@13: +kokkos+cuda') | ||||
|     depends_on('hypre@develop', when='@master: +hypre') | ||||
|     depends_on('netcdf-c+mpi+parallel-netcdf', when="+exodus+mpi@12.12.1:") | ||||
|     depends_on('superlu-dist@4.4:5.3', when='@12.6.2:12.12.1+superlu-dist') | ||||
|     depends_on('superlu-dist@5.4:6.2.0', when='@12.12.2:13.0.0+superlu-dist') | ||||
|     depends_on('superlu-dist@6.3.0:', when='@13.0.1:99 +superlu-dist') | ||||
|     depends_on('superlu-dist@:4.3', when='@11.14.1:12.6.1+superlu-dist') | ||||
|     depends_on('superlu-dist@develop', when='@master: +superlu-dist') | ||||
|  | ||||
|     # ###################### Patches ########################## | ||||
|  | ||||
|     patch('umfpack_from_suitesparse.patch', when='@11.14.1:12.8.1') | ||||
|     for _compiler in ['xl', 'xl_r', 'clang']: | ||||
|         patch('xlf_seacas.patch', when='@12.10.1:12.12.1 %' + _compiler) | ||||
|         patch('xlf_tpetra.patch', when='@12.12.1 %' + _compiler) | ||||
|     patch('fix_clang_errors_12_18_1.patch', when='@12.18.1%clang') | ||||
|     patch('cray_secas_12_12_1.patch', when='@12.12.1%cce') | ||||
|     patch('cray_secas.patch', when='@12.14.1:%cce') | ||||
|  | ||||
|     # workaround an NVCC bug with c++14 (https://github.com/trilinos/Trilinos/issues/6954) | ||||
|     # avoid calling deprecated functions with CUDA-11 | ||||
|     patch('fix_cxx14_cuda11.patch', when='@13.0.0:13.0.1 cxxstd=14 ^cuda@11:') | ||||
|     # Allow building with +teko gotype=long | ||||
|     patch('https://github.com/trilinos/Trilinos/commit/b17f20a0b91e0b9fc5b1b0af3c8a34e2a4874f3f.patch', | ||||
|           sha256='dee6c55fe38eb7f6367e1896d6bc7483f6f9ab8fa252503050cc0c68c6340610', | ||||
|           when='@13.0.0:13.0.1 +teko gotype=long') | ||||
|  | ||||
|     def flag_handler(self, name, flags): | ||||
|         is_cce = self.spec.satisfies('%cce') | ||||
|  | ||||
|         if name == 'cxxflags': | ||||
|             spec = self.spec | ||||
|             if '+mumps' in spec: | ||||
|                 # see https://github.com/trilinos/Trilinos/blob/master/packages/amesos/README-MUMPS | ||||
|                 flags.append('-DMUMPS_5_0') | ||||
|             if '+stk platform=darwin' in spec: | ||||
|                 flags.append('-DSTK_NO_BOOST_STACKTRACE') | ||||
|             if '+stk%intel' in spec: | ||||
|                 # Workaround for Intel compiler segfaults with STK and IPO | ||||
|                 flags.append('-no-ipo') | ||||
|             if '+wrapper' in spec: | ||||
|                 flags.append('--expt-extended-lambda') | ||||
|         elif name == 'ldflags' and is_cce: | ||||
|             flags.append('-fuse-ld=gold') | ||||
|  | ||||
|         if is_cce: | ||||
|             return (None, None, flags) | ||||
|         return (flags, None, None) | ||||
|  | ||||
|     def url_for_version(self, version): | ||||
|         url = "https://github.com/trilinos/Trilinos/archive/trilinos-release-{0}.tar.gz" | ||||
|         return url.format(version.dashed) | ||||
|  | ||||
|     def setup_dependent_run_environment(self, env, dependent_spec): | ||||
|         if '+cuda' in self.spec: | ||||
|             # currently Trilinos doesn't perform the memory fence so | ||||
|             # it relies on blocking CUDA kernel launch. This is needed | ||||
|             # in case the dependent app also run a CUDA backend via Trilinos | ||||
|             env.set('CUDA_LAUNCH_BLOCKING', '1') | ||||
|  | ||||
|     def setup_dependent_package(self, module, dependent_spec): | ||||
|         if '+wrapper' in self.spec: | ||||
|             self.spec.kokkos_cxx = self.spec["kokkos-nvcc-wrapper"].kokkos_cxx | ||||
|         else: | ||||
|             self.spec.kokkos_cxx = spack_cxx | ||||
|  | ||||
|     def setup_build_environment(self, env): | ||||
|         spec = self.spec | ||||
|         if '+cuda' in spec and '+wrapper' in spec: | ||||
|             if '+mpi' in spec: | ||||
|                 env.set('OMPI_CXX', spec["kokkos-nvcc-wrapper"].kokkos_cxx) | ||||
|                 env.set('MPICH_CXX', spec["kokkos-nvcc-wrapper"].kokkos_cxx) | ||||
|                 env.set('MPICXX_CXX', spec["kokkos-nvcc-wrapper"].kokkos_cxx) | ||||
|             else: | ||||
|                 env.set('CXX', spec["kokkos-nvcc-wrapper"].kokkos_cxx) | ||||
|  | ||||
|     def cmake_args(self): | ||||
|         options = [] | ||||
|  | ||||
|         spec = self.spec | ||||
|         define = CMakePackage.define | ||||
|         define_from_variant = self.define_from_variant | ||||
|  | ||||
|         def _make_definer(prefix): | ||||
|             def define_enable(suffix, value=None): | ||||
|                 key = prefix + suffix | ||||
|                 if value is None: | ||||
|                     # Default to lower-case spec | ||||
|                     value = suffix.lower() | ||||
|                 elif isinstance(value, bool): | ||||
|                     # Explicit true/false | ||||
|                     return define(key, value) | ||||
|                 return define_from_variant(key, value) | ||||
|             return define_enable | ||||
|  | ||||
|         # Return "Trilinos_ENABLE_XXX" for spec "+xxx" or boolean value | ||||
|         define_trilinos_enable = _make_definer("Trilinos_ENABLE_") | ||||
|         # Same but for TPLs | ||||
|         define_tpl_enable = _make_definer("TPL_ENABLE_") | ||||
|  | ||||
|         # #################### Base Settings ####################### | ||||
|  | ||||
|         options.extend([ | ||||
|             define('Trilinos_VERBOSE_CONFIGURE', False), | ||||
|             define_from_variant('BUILD_SHARED_LIBS', 'shared'), | ||||
|             define_from_variant('CMAKE_CXX_STANDARD', 'cxxstd'), | ||||
|             define_trilinos_enable('ALL_OPTIONAL_PACKAGES', False), | ||||
|             define_trilinos_enable('ALL_PACKAGES', False), | ||||
|             define_trilinos_enable('CXX11', True), | ||||
|             define_trilinos_enable('DEBUG', 'debug'), | ||||
|             define_trilinos_enable('EXAMPLES', False), | ||||
|             define_trilinos_enable('SECONDARY_TESTED_CODE', True), | ||||
|             define_trilinos_enable('TESTS', False), | ||||
|             define_trilinos_enable('Fortran'), | ||||
|             define_trilinos_enable('OpenMP'), | ||||
|             define_trilinos_enable('EXPLICIT_INSTANTIATION', | ||||
|                                    'explicit_template_instantiation') | ||||
|         ]) | ||||
|  | ||||
|         # ################## Trilinos Packages ##################### | ||||
|  | ||||
|         options.extend([ | ||||
|             define_trilinos_enable('Amesos'), | ||||
|             define_trilinos_enable('Amesos2'), | ||||
|             define_trilinos_enable('Anasazi'), | ||||
|             define_trilinos_enable('AztecOO', 'aztec'), | ||||
|             define_trilinos_enable('Belos'), | ||||
|             define_trilinos_enable('Epetra'), | ||||
|             define_trilinos_enable('EpetraExt'), | ||||
|             define_trilinos_enable('FEI', False), | ||||
|             define_trilinos_enable('Gtest'), | ||||
|             define_trilinos_enable('Ifpack'), | ||||
|             define_trilinos_enable('Ifpack2'), | ||||
|             define_trilinos_enable('Intrepid'), | ||||
|             define_trilinos_enable('Intrepid2'), | ||||
|             define_trilinos_enable('Isorropia'), | ||||
|             define_trilinos_enable('Kokkos'), | ||||
|             define_trilinos_enable('MiniTensor'), | ||||
|             define_trilinos_enable('Mesquite'), | ||||
|             define_trilinos_enable('ML'), | ||||
|             define_trilinos_enable('MueLu'), | ||||
|             define_trilinos_enable('NOX'), | ||||
|             define_trilinos_enable('Pamgen', False), | ||||
|             define_trilinos_enable('Panzer', False), | ||||
|             define_trilinos_enable('Pike', False), | ||||
|             define_trilinos_enable('Piro'), | ||||
|             define_trilinos_enable('Phalanx'), | ||||
|             define_trilinos_enable('PyTrilinos', 'python'), | ||||
|             define_trilinos_enable('ROL'), | ||||
|             define_trilinos_enable('Rythmos'), | ||||
|             define_trilinos_enable('Sacado'), | ||||
|             define_trilinos_enable('SCOREC'), | ||||
|             define_trilinos_enable('Shards'), | ||||
|             define_trilinos_enable('ShyLU'), | ||||
|             define_trilinos_enable('STK'), | ||||
|             define_trilinos_enable('Stokhos'), | ||||
|             define_trilinos_enable('Stratimikos'), | ||||
|             define_trilinos_enable('Teko'), | ||||
|             define_trilinos_enable('Tempus'), | ||||
|             define_trilinos_enable('Tpetra'), | ||||
|             define_trilinos_enable('TrilinosCouplings'), | ||||
|             define_trilinos_enable('Zoltan'), | ||||
|             define_trilinos_enable('Zoltan2'), | ||||
|             define_tpl_enable('Cholmod', False), | ||||
|             define_from_variant('EpetraExt_BUILD_BTF', 'epetraextbtf'), | ||||
|             define_from_variant('EpetraExt_BUILD_EXPERIMENTAL', | ||||
|                                 'epetraextexperimental'), | ||||
|             define_from_variant('EpetraExt_BUILD_GRAPH_REORDERINGS', | ||||
|                                 'epetraextgraphreorderings'), | ||||
|             define_from_variant('Amesos2_ENABLE_Basker', 'basker'), | ||||
|         ]) | ||||
|  | ||||
|         if '+dtk' in spec: | ||||
|             options.extend([ | ||||
|                 define('Trilinos_EXTRA_REPOSITORIES', 'DataTransferKit'), | ||||
|                 define_trilinos_enable('DataTransferKit', True), | ||||
|             ]) | ||||
|  | ||||
|         if '+exodus' in spec: | ||||
|             options.extend([ | ||||
|                 define_trilinos_enable('SEACAS', True), | ||||
|                 define_trilinos_enable('SEACASExodus', True), | ||||
|                 define_trilinos_enable('SEACASIoss', True), | ||||
|                 define_trilinos_enable('SEACASEpu', True), | ||||
|                 define_trilinos_enable('SEACASExodiff', True), | ||||
|                 define_trilinos_enable('SEACASNemspread', True), | ||||
|                 define_trilinos_enable('SEACASNemslice', True), | ||||
|             ]) | ||||
|         else: | ||||
|             options.extend([ | ||||
|                 define_trilinos_enable('SEACASExodus', False), | ||||
|                 define_trilinos_enable('SEACASIoss', False), | ||||
|             ]) | ||||
|  | ||||
|         if '+chaco' in spec: | ||||
|             options.extend([ | ||||
|                 define_trilinos_enable('SEACAS', True), | ||||
|                 define_trilinos_enable('SEACASChaco', True), | ||||
|             ]) | ||||
|         else: | ||||
|             # don't disable SEACAS, could be needed elsewhere | ||||
|             options.extend([ | ||||
|                 define_trilinos_enable('SEACASChaco', False), | ||||
|                 define_trilinos_enable('SEACASNemslice', False) | ||||
|             ]) | ||||
|  | ||||
|         if '+stratimikos' in spec: | ||||
|             # Explicitly enable Thyra (ThyraCore is required). If you don't do | ||||
|             # this, then you get "NOT setting ${pkg}_ENABLE_Thyra=ON since | ||||
|             # Thyra is NOT enabled at this point!" leading to eventual build | ||||
|             # errors if using MueLu because `Xpetra_ENABLE_Thyra` is set to | ||||
|             # off. | ||||
|             options.append(define_trilinos_enable('Thyra', True)) | ||||
|  | ||||
|             # Add thyra adapters based on package enables | ||||
|             options.extend( | ||||
|                 define_trilinos_enable('Thyra' + pkg + 'Adapters', pkg.lower()) | ||||
|                 for pkg in ['Epetra', 'EpetraExt', 'Tpetra']) | ||||
|  | ||||
|         # ######################### TPLs ############################# | ||||
|  | ||||
|         def define_tpl(trilinos_name, spack_name, have_dep): | ||||
|             options.append(define('TPL_ENABLE_' + trilinos_name, have_dep)) | ||||
|             if not have_dep: | ||||
|                 return | ||||
|             depspec = spec[spack_name] | ||||
|             libs = depspec.libs | ||||
|             try: | ||||
|                 options.extend([ | ||||
|                     define(trilinos_name + '_INCLUDE_DIRS', | ||||
|                            depspec.headers.directories), | ||||
|                 ]) | ||||
|             except NoHeadersError: | ||||
|                 # Handle case were depspec does not have headers | ||||
|                 pass | ||||
|  | ||||
|             options.extend([ | ||||
|                 define(trilinos_name + '_ROOT', depspec.prefix), | ||||
|                 define(trilinos_name + '_LIBRARY_NAMES', libs.names), | ||||
|                 define(trilinos_name + '_LIBRARY_DIRS', libs.directories), | ||||
|             ]) | ||||
|  | ||||
|         # Enable these TPLs explicitly from variant options. | ||||
|         # Format is (TPL name, variant name, Spack spec name) | ||||
|         tpl_variant_map = [ | ||||
|             ('ADIOS2', 'adios2', 'adios2'), | ||||
|             ('Boost', 'boost', 'boost'), | ||||
|             ('CUDA', 'cuda', 'cuda'), | ||||
|             ('HDF5', 'hdf5', 'hdf5'), | ||||
|             ('HYPRE', 'hypre', 'hypre'), | ||||
|             ('MUMPS', 'mumps', 'mumps'), | ||||
|             ('UMFPACK', 'suite-sparse', 'suite-sparse'), | ||||
|             ('SuperLU', 'superlu', 'superlu'), | ||||
|             ('SuperLUDist', 'superlu-dist', 'superlu-dist'), | ||||
|             ('X11', 'x11', 'libx11'), | ||||
|         ] | ||||
|         if spec.satisfies('@13.0.2:'): | ||||
|             tpl_variant_map.append(('STRUMPACK', 'strumpack', 'strumpack')) | ||||
|  | ||||
|         for tpl_name, var_name, spec_name in tpl_variant_map: | ||||
|             define_tpl(tpl_name, spec_name, spec.variants[var_name].value) | ||||
|  | ||||
|         # Enable these TPLs based on whether they're in our spec; prefer to | ||||
|         # require this way so that packages/features disable availability | ||||
|         tpl_dep_map = [ | ||||
|             ('BLAS', 'blas'), | ||||
|             ('CGNS', 'cgns'), | ||||
|             ('LAPACK', 'lapack'), | ||||
|             ('Matio', 'matio'), | ||||
|             ('METIS', 'metis'), | ||||
|             ('Netcdf', 'netcdf-c'), | ||||
|             ('SCALAPACK', 'scalapack'), | ||||
|             ('Zlib', 'zlib'), | ||||
|         ] | ||||
|         if spec.satisfies('@12.12.1:'): | ||||
|             tpl_dep_map.append(('Pnetcdf', 'parallel-netcdf')) | ||||
|         if spec.satisfies('@13:'): | ||||
|             tpl_dep_map.append(('HWLOC', 'hwloc')) | ||||
|  | ||||
|         for tpl_name, dep_name in tpl_dep_map: | ||||
|             define_tpl(tpl_name, dep_name, dep_name in spec) | ||||
|  | ||||
|         # MPI settings | ||||
|         options.append(define_tpl_enable('MPI')) | ||||
|         if '+mpi' in spec: | ||||
|             # Force Trilinos to use the MPI wrappers instead of raw compilers | ||||
|             # to propagate library link flags for linkers that require fully | ||||
|             # resolved symbols in shared libs (such as macOS and some newer | ||||
|             # Ubuntu) | ||||
|             options.extend([ | ||||
|                 define('CMAKE_C_COMPILER', spec['mpi'].mpicc), | ||||
|                 define('CMAKE_CXX_COMPILER', spec['mpi'].mpicxx), | ||||
|                 define('CMAKE_Fortran_COMPILER', spec['mpi'].mpifc), | ||||
|                 define('MPI_BASE_DIR', spec['mpi'].prefix), | ||||
|             ]) | ||||
|  | ||||
|         # ParMETIS dependencies have to be transitive explicitly | ||||
|         have_parmetis = 'parmetis' in spec | ||||
|         options.append(define_tpl_enable('ParMETIS', have_parmetis)) | ||||
|         if have_parmetis: | ||||
|             options.extend([ | ||||
|                 define('ParMETIS_LIBRARY_DIRS', [ | ||||
|                     spec['parmetis'].prefix.lib, spec['metis'].prefix.lib | ||||
|                 ]), | ||||
|                 define('ParMETIS_LIBRARY_NAMES', ['parmetis', 'metis']), | ||||
|                 define('TPL_ParMETIS_INCLUDE_DIRS', | ||||
|                        spec['parmetis'].headers.directories + | ||||
|                        spec['metis'].headers.directories), | ||||
|             ]) | ||||
|  | ||||
|         if spec.satisfies('^superlu-dist@4.0:'): | ||||
|             options.extend([ | ||||
|                 define('HAVE_SUPERLUDIST_LUSTRUCTINIT_2ARG', True), | ||||
|             ]) | ||||
|  | ||||
|         if spec.satisfies('^parallel-netcdf'): | ||||
|             options.extend([ | ||||
|                 define('TPL_Netcdf_Enables_Netcdf4', True), | ||||
|                 define('TPL_Netcdf_PARALLEL', True), | ||||
|                 define('PNetCDF_ROOT', spec['parallel-netcdf'].prefix), | ||||
|             ]) | ||||
|  | ||||
|         # ################# Explicit template instantiation ################# | ||||
|  | ||||
|         complex_s = spec.variants['complex'].value | ||||
|         float_s = spec.variants['float'].value | ||||
|  | ||||
|         options.extend([ | ||||
|             define('Teuchos_ENABLE_COMPLEX', complex_s), | ||||
|             define('Teuchos_ENABLE_FLOAT', float_s), | ||||
|         ]) | ||||
|  | ||||
|         if '+tpetra +explicit_template_instantiation' in spec: | ||||
|             options.append(define_from_variant('Tpetra_INST_OPENMP', 'openmp')) | ||||
|             options.extend([ | ||||
|                 define('Tpetra_INST_DOUBLE', True), | ||||
|                 define('Tpetra_INST_COMPLEX_DOUBLE', complex_s), | ||||
|                 define('Tpetra_INST_COMPLEX_FLOAT', float_s and complex_s), | ||||
|                 define('Tpetra_INST_FLOAT', float_s), | ||||
|                 define('Tpetra_INST_SERIAL', True), | ||||
|             ]) | ||||
|  | ||||
|             gotype = spec.variants['gotype'].value | ||||
|             if gotype == 'all': | ||||
|                 # default in older Trilinos versions to enable multiple GOs | ||||
|                 options.extend([ | ||||
|                     define('Tpetra_INST_INT_INT', True), | ||||
|                     define('Tpetra_INST_INT_LONG', True), | ||||
|                     define('Tpetra_INST_INT_LONG_LONG', True), | ||||
|                 ]) | ||||
|             else: | ||||
|                 options.extend([ | ||||
|                     define('Tpetra_INST_INT_INT', gotype == 'int'), | ||||
|                     define('Tpetra_INST_INT_LONG', gotype == 'long'), | ||||
|                     define('Tpetra_INST_INT_LONG_LONG', gotype == 'long_long'), | ||||
|                 ]) | ||||
|  | ||||
|         # ################# Kokkos ###################### | ||||
|  | ||||
|         if '+kokkos' in spec: | ||||
|             arch = Kokkos.get_microarch(spec.target) | ||||
|             if arch: | ||||
|                 options.append(define("Kokkos_ARCH_" + arch.upper(), True)) | ||||
|  | ||||
|             define_kok_enable = _make_definer("Kokkos_ENABLE_") | ||||
|             options.extend([ | ||||
|                 define_kok_enable('CUDA'), | ||||
|                 define_kok_enable('OPENMP' if spec.version >= Version('13') | ||||
|                                   else 'OpenMP'), | ||||
|             ]) | ||||
|             if '+cuda' in spec: | ||||
|                 options.extend([ | ||||
|                     define_kok_enable('CUDA_UVM', True), | ||||
|                     define_kok_enable('CUDA_LAMBDA', True), | ||||
|                     define_kok_enable('CUDA_RELOCATABLE_DEVICE_CODE', 'cuda_rdc') | ||||
|                 ]) | ||||
|                 arch_map = Kokkos.spack_cuda_arch_map | ||||
|                 options.extend( | ||||
|                     define("Kokkos_ARCH_" + arch_map[arch].upper(), True) | ||||
|                     for arch in spec.variants['cuda_arch'].value | ||||
|                 ) | ||||
|  | ||||
|         # ################# System-specific ###################### | ||||
|  | ||||
|         # Fortran lib (assumes clang is built with gfortran!) | ||||
|         if ('+fortran' in spec | ||||
|                 and spec.compiler.name in ['gcc', 'clang', 'apple-clang']): | ||||
|             fc = Executable(spec['mpi'].mpifc) if ( | ||||
|                 '+mpi' in spec) else Executable(spack_fc) | ||||
|             libgfortran = fc('--print-file-name', | ||||
|                              'libgfortran.' + dso_suffix, | ||||
|                              output=str).strip() | ||||
|             # if libgfortran is equal to "libgfortran.<dso_suffix>" then | ||||
|             # print-file-name failed, use static library instead | ||||
|             if libgfortran == 'libgfortran.' + dso_suffix: | ||||
|                 libgfortran = fc('--print-file-name', | ||||
|                                  'libgfortran.a', | ||||
|                                  output=str).strip() | ||||
|             # -L<libdir> -lgfortran required for OSX | ||||
|             # https://github.com/spack/spack/pull/25823#issuecomment-917231118 | ||||
|             options.append( | ||||
|                 define('Trilinos_EXTRA_LINK_FLAGS', | ||||
|                        '-L%s/ -lgfortran' % os.path.dirname(libgfortran))) | ||||
|  | ||||
|         if sys.platform == 'darwin' and macos_version() >= Version('10.12'): | ||||
|             # use @rpath on Sierra due to limit of dynamic loader | ||||
|             options.append(define('CMAKE_MACOSX_RPATH', True)) | ||||
|         else: | ||||
|             options.append(define('CMAKE_INSTALL_NAME_DIR', self.prefix.lib)) | ||||
|  | ||||
|         return options | ||||
|  | ||||
|     @run_after('install') | ||||
|     def filter_python(self): | ||||
|         # When trilinos is built with Python, libpytrilinos is included | ||||
|         # through cmake configure files. Namely, Trilinos_LIBRARIES in | ||||
|         # TrilinosConfig.cmake contains pytrilinos. This leads to a | ||||
|         # run-time error: Symbol not found: _PyBool_Type and prevents | ||||
|         # Trilinos to be used in any C++ code, which links executable | ||||
|         # against the libraries listed in Trilinos_LIBRARIES.  See | ||||
|         # https://github.com/trilinos/Trilinos/issues/569 and | ||||
|         # https://github.com/trilinos/Trilinos/issues/866 | ||||
|         # A workaround is to remove PyTrilinos from the COMPONENTS_LIST | ||||
|         # and to remove -lpytrilonos from Makefile.export.Trilinos | ||||
|         if '+python' in self.spec: | ||||
|             filter_file(r'(SET\(COMPONENTS_LIST.*)(PyTrilinos;)(.*)', | ||||
|                         (r'\1\3'), | ||||
|                         '%s/cmake/Trilinos/TrilinosConfig.cmake' % | ||||
|                         self.prefix.lib) | ||||
|             filter_file(r'-lpytrilinos', '', | ||||
|                         '%s/Makefile.export.Trilinos' % | ||||
|                         self.prefix.include) | ||||
|  | ||||
|     def setup_run_environment(self, env): | ||||
|         if '+exodus' in self.spec: | ||||
|             env.prepend_path('PYTHONPATH', self.prefix.lib) | ||||
|  | ||||
|         if '+cuda' in self.spec: | ||||
|             # currently Trilinos doesn't perform the memory fence so | ||||
|             # it relies on blocking CUDA kernel launch. | ||||
|             env.set('CUDA_LAUNCH_BLOCKING', '1') | ||||
| @@ -3,6 +3,7 @@ | ||||
| # | ||||
| # SPDX-License-Identifier: (Apache-2.0 OR MIT) | ||||
| """This test does sanity checks on Spack's builtin package database.""" | ||||
| import ast | ||||
| import os.path | ||||
| import pickle | ||||
| import re | ||||
| @@ -58,18 +59,30 @@ def test_packages_are_pickleable(): | ||||
| 
 | ||||
| 
 | ||||
| def test_packages_are_unparseable(): | ||||
|     failed_to_unparse = list() | ||||
|     """Ensure that all packages can unparse and that unparsed code is valid Python.""" | ||||
|     failed_to_unparse = [] | ||||
|     failed_to_compile = [] | ||||
| 
 | ||||
|     for name in spack.repo.all_package_names(): | ||||
|         try: | ||||
|             ph.canonical_source(name, filter_multimethods=False) | ||||
|             source = ph.canonical_source(name, filter_multimethods=False) | ||||
|         except Exception: | ||||
|             failed_to_unparse.append(name) | ||||
| 
 | ||||
|         try: | ||||
|             compile(source, "internal", "exec", ast.PyCF_ONLY_AST) | ||||
|         except Exception: | ||||
|             failed_to_compile.append(name) | ||||
| 
 | ||||
|     if failed_to_unparse: | ||||
|         tty.msg('The following packages failed to unparse: ' + | ||||
|                 ', '.join(failed_to_unparse)) | ||||
|         assert False | ||||
| 
 | ||||
|     assert len(failed_to_unparse) == 0 | ||||
|     if failed_to_compile: | ||||
|         tty.msg('The following unparsed packages failed to compile: ' + | ||||
|                 ', '.join(failed_to_compile)) | ||||
|         assert False | ||||
| 
 | ||||
| 
 | ||||
| def test_repo_getpkg_names_and_classes(): | ||||
|   | ||||
| @@ -156,32 +156,28 @@ def method2(): | ||||
|         return "TWELVE" | ||||
| ''' | ||||
| 
 | ||||
| many_strings_no_docstrings = """\ | ||||
| var = 'THREE' | ||||
| 
 | ||||
| class ManyDocstrings: | ||||
|     x = 'SEVEN' | ||||
| 
 | ||||
|     def method1(): | ||||
|         print('NINE') | ||||
|         for i in range(10): | ||||
|             print(i) | ||||
| 
 | ||||
|     def method2(): | ||||
|         return 'TWELVE' | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| def test_remove_docstrings(): | ||||
|     tree = ast.parse(many_strings) | ||||
|     tree = ph.RemoveDocstrings().visit(tree) | ||||
| 
 | ||||
|     unparsed = unparse(tree) | ||||
| 
 | ||||
|     # make sure the methods are preserved | ||||
|     assert "method1" in unparsed | ||||
|     assert "method2" in unparsed | ||||
| 
 | ||||
|     # all of these are unassigned and should be removed | ||||
|     assert "ONE" not in unparsed | ||||
|     assert "TWO" not in unparsed | ||||
|     assert "FOUR" not in unparsed | ||||
|     assert "FIVE" not in unparsed | ||||
|     assert "SIX" not in unparsed | ||||
|     assert "EIGHT" not in unparsed | ||||
|     assert "TEN" not in unparsed | ||||
|     assert "ELEVEN" not in unparsed | ||||
| 
 | ||||
|     # these are used in legitimate expressions | ||||
|     assert "THREE" in unparsed | ||||
|     assert "SEVEN" in unparsed | ||||
|     assert "NINE" in unparsed | ||||
|     assert "TWELVE" in unparsed | ||||
|     unparsed = unparse(tree, py_ver_consistent=True) | ||||
|     assert unparsed == many_strings_no_docstrings | ||||
| 
 | ||||
| 
 | ||||
| many_directives = """\ | ||||
| @@ -199,31 +195,137 @@ def foo(): | ||||
| )) | ||||
| 
 | ||||
| 
 | ||||
| def test_remove_directives(): | ||||
| def test_remove_all_directives(): | ||||
|     """Ensure all directives are removed from packages before hashing.""" | ||||
|     for name in spack.directives.directive_names: | ||||
|         assert name in many_directives | ||||
| 
 | ||||
|     tree = ast.parse(many_directives) | ||||
|     spec = Spec("has-many-directives") | ||||
|     tree = ph.RemoveDirectives(spec).visit(tree) | ||||
|     unparsed = unparse(tree) | ||||
|     unparsed = unparse(tree, py_ver_consistent=True) | ||||
| 
 | ||||
|     for name in spack.directives.directive_names: | ||||
|         assert name not in unparsed | ||||
| 
 | ||||
| 
 | ||||
| many_attributes = """\ | ||||
| class HasManyMetadataAttributes: | ||||
|     homepage = "https://example.com" | ||||
|     url = "https://example.com/foo.tar.gz" | ||||
|     git = "https://example.com/foo/bar.git" | ||||
| 
 | ||||
|     maintainers = ["alice", "bob"] | ||||
|     tags = ["foo", "bar", "baz"] | ||||
| 
 | ||||
|     depends_on("foo") | ||||
|     conflicts("foo") | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| many_attributes_canonical = """\ | ||||
| class HasManyMetadataAttributes: | ||||
|     pass | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| def test_remove_spack_attributes(): | ||||
|     tree = ast.parse(many_attributes) | ||||
|     spec = Spec("has-many-metadata-attributes") | ||||
|     tree = ph.RemoveDirectives(spec).visit(tree) | ||||
|     unparsed = unparse(tree, py_ver_consistent=True) | ||||
| 
 | ||||
|     assert unparsed == many_attributes_canonical | ||||
| 
 | ||||
| 
 | ||||
| complex_package_logic = """\ | ||||
| class ComplexPackageLogic: | ||||
|     for variant in ["+foo", "+bar", "+baz"]: | ||||
|         conflicts("quux" + variant) | ||||
| 
 | ||||
|     for variant in ["+foo", "+bar", "+baz"]: | ||||
|         # logic in the loop prevents our dumb analyzer from having it removed. This | ||||
|         # is uncommon so we don't (yet?) implement logic to detect that spec is unused. | ||||
|         print("oops can't remove this.") | ||||
|         conflicts("quux" + variant) | ||||
| 
 | ||||
|         # Hard to make a while loop that makes sense, so ignore the infinite loop here. | ||||
|         # Likely nobody uses while instead of for, but we test it just in case. | ||||
|         while x <= 10: | ||||
|             depends_on("garply@%d.0" % x) | ||||
| 
 | ||||
|     # all of these should go away, as they only contain directives | ||||
|     with when("@10.0"): | ||||
|         depends_on("foo") | ||||
|         with when("+bar"): | ||||
|             depends_on("bar") | ||||
|             with when("+baz"): | ||||
|                 depends_on("baz") | ||||
| 
 | ||||
|     # this whole statement should disappear | ||||
|     if sys.platform == "linux": | ||||
|         conflicts("baz@9.0") | ||||
| 
 | ||||
|     # the else block here should disappear | ||||
|     if sys.platform == "linux": | ||||
|         print("foo") | ||||
|     else: | ||||
|         conflicts("foo@9.0") | ||||
| 
 | ||||
|     # both blocks of this statement should disappear | ||||
|     if sys.platform == "darwin": | ||||
|         conflicts("baz@10.0") | ||||
|     else: | ||||
|         conflicts("bar@10.0") | ||||
| 
 | ||||
|     # This one is complicated as the body goes away but the else block doesn't. | ||||
|     # Again, this could be optimized, but we're just testing removal logic here. | ||||
|     if sys.platform() == "darwin": | ||||
|         conflicts("baz@10.0") | ||||
|     else: | ||||
|         print("oops can't remove this.") | ||||
|         conflicts("bar@10.0") | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| complex_package_logic_filtered = """\ | ||||
| class ComplexPackageLogic: | ||||
|     for variant in ['+foo', '+bar', '+baz']: | ||||
|         print("oops can't remove this.") | ||||
|     if sys.platform == 'linux': | ||||
|         print('foo') | ||||
|     if sys.platform() == 'darwin': | ||||
|         pass | ||||
|     else: | ||||
|         print("oops can't remove this.") | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| def test_remove_complex_package_logic_filtered(): | ||||
|     tree = ast.parse(complex_package_logic) | ||||
|     spec = Spec("has-many-metadata-attributes") | ||||
|     tree = ph.RemoveDirectives(spec).visit(tree) | ||||
|     unparsed = unparse(tree, py_ver_consistent=True) | ||||
| 
 | ||||
|     assert unparsed == complex_package_logic_filtered | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.parametrize("package_spec,expected_hash", [ | ||||
|     ("amdfftw",      "nfrk76xyu6wxs4xb4nyichm3om3kb7yp"), | ||||
|     ("amdfftw",      "tivb752zddjgvfkogfs7cnnvp5olj6co"), | ||||
|     ("grads",        "rrlmwml3f2frdnqavmro3ias66h5b2ce"), | ||||
|     ("llvm",         "ngact4ds3xwgsbn5bruxpfs6f4u4juba"), | ||||
|     ("llvm",         "g3hoqf4rhprd3da7byp5nzco6tcwliiy"), | ||||
|     # has @when("@4.1.0") and raw unicode literals | ||||
|     ("mfem",         "65xryd5zxarwzqlh2pojq7ykohpod4xz"), | ||||
|     ("mfem@4.0.0",   "65xryd5zxarwzqlh2pojq7ykohpod4xz"), | ||||
|     ("mfem@4.1.0",   "2j655nix3oe57iwvs2mlgx2mresk7czl"), | ||||
|     ("mfem",         "tiiv7uq7v2xtv24vdij5ptcv76dpazrw"), | ||||
|     ("mfem@4.0.0",   "tiiv7uq7v2xtv24vdij5ptcv76dpazrw"), | ||||
|     ("mfem@4.1.0",   "gxastq64to74qt4he4knpyjfdhh5auel"), | ||||
|     # has @when("@1.5.0:") | ||||
|     ("py-torch",     "lnwmqk4wadtlsc2badrt7foid5tl5vaw"), | ||||
|     ("py-torch@1.0", "lnwmqk4wadtlsc2badrt7foid5tl5vaw"), | ||||
|     ("py-torch@1.6", "5nwndnknxdfs5or5nrl4pecvw46xc5i2"), | ||||
|     ("py-torch",     "qs7djgqn7dy7r3ps4g7hv2pjvjk4qkhd"), | ||||
|     ("py-torch@1.0", "qs7djgqn7dy7r3ps4g7hv2pjvjk4qkhd"), | ||||
|     ("py-torch@1.6", "p4ine4hc6f2ik2f2wyuwieslqbozll5w"), | ||||
|     # has a print with multiple arguments | ||||
|     ("legion",       "ba4tleyb3g5mdhhsje6t6jyitqj3yfpz"), | ||||
|     ("legion",       "zdpawm4avw3fllxcutvmqb5c3bj5twqt"), | ||||
|     # has nested `with when()` blocks and loops | ||||
|     ("trilinos",     "vqrgscjrla4hi7bllink7v6v6dwxgc2p"), | ||||
| ]) | ||||
| def test_package_hash_consistency(package_spec, expected_hash): | ||||
|     """Ensure that that package hash is consistent python version to version. | ||||
|   | ||||
| @@ -34,51 +34,106 @@ def unused_string(node): | ||||
|         self.generic_visit(node) | ||||
|         return node | ||||
| 
 | ||||
|     def visit_FunctionDef(self, node):  # noqa | ||||
|     def visit_FunctionDef(self, node): | ||||
|         return self.remove_docstring(node) | ||||
| 
 | ||||
|     def visit_ClassDef(self, node):  # noqa | ||||
|     def visit_ClassDef(self, node): | ||||
|         return self.remove_docstring(node) | ||||
| 
 | ||||
|     def visit_Module(self, node):  # noqa | ||||
|     def visit_Module(self, node): | ||||
|         return self.remove_docstring(node) | ||||
| 
 | ||||
| 
 | ||||
| class RemoveDirectives(ast.NodeTransformer): | ||||
|     """Remove Spack directives from a package AST.""" | ||||
|     """Remove Spack directives from a package AST. | ||||
| 
 | ||||
|     This removes Spack directives (e.g., ``depends_on``, ``conflicts``, etc.) and | ||||
|     metadata attributes (e.g., ``tags``, ``homepage``, ``url``) in a top-level class | ||||
|     definition within a ``package.py``, but it does not modify nested classes or | ||||
|     functions. | ||||
| 
 | ||||
|     If removing directives causes a ``for``, ``with``, or ``while`` statement to have an | ||||
|     empty body, we remove the entire statement. Similarly, If removing directives causes | ||||
|     an ``if`` statement to have an empty body or ``else`` block, we'll remove the block | ||||
|     (or replace the body with ``pass`` if there is an ``else`` block but no body). | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, spec): | ||||
|         # list of URL attributes and metadata attributes | ||||
|         # these will be removed from packages. | ||||
|         self.metadata_attrs = [s.url_attr for s in spack.fetch_strategy.all_strategies] | ||||
|         self.metadata_attrs += spack.package.Package.metadata_attrs | ||||
| 
 | ||||
|         self.spec = spec | ||||
|         self.in_classdef = False  # used to avoid nested classdefs | ||||
| 
 | ||||
|     def is_directive(self, node): | ||||
|         """Check to determine if the node is a valid directive | ||||
|     def visit_Expr(self, node): | ||||
|         # Directives are represented in the AST as named function call expressions (as | ||||
|         # opposed to function calls through a variable callback). We remove them. | ||||
|         # | ||||
|         # Note that changes to directives (e.g., a preferred version change or a hash | ||||
|         # chnage on an archive) are already represented in the spec *outside* the | ||||
|         # package hash. | ||||
|         return None if ( | ||||
|             node.value and isinstance(node.value, ast.Call) and | ||||
|             isinstance(node.value.func, ast.Name) and | ||||
|             node.value.func.id in spack.directives.directive_names | ||||
|         ) else node | ||||
| 
 | ||||
|         Directives are assumed to be represented in the AST as a named function | ||||
|         call expression.  This means that they will NOT be represented by a | ||||
|         named function call within a function call expression (e.g., as | ||||
|         callbacks are sometimes represented). | ||||
|     def visit_Assign(self, node): | ||||
|         # Remove assignments to metadata attributes, b/c they don't affect the build. | ||||
|         return None if ( | ||||
|             node.targets and isinstance(node.targets[0], ast.Name) and | ||||
|             node.targets[0].id in self.metadata_attrs | ||||
|         ) else node | ||||
| 
 | ||||
|         Args: | ||||
|             node (ast.AST): the AST node being checked | ||||
|     def visit_With(self, node): | ||||
|         self.generic_visit(node)            # visit children | ||||
|         return node if node.body else None  # remove with statement if it has no body | ||||
| 
 | ||||
|         Returns: | ||||
|             bool: ``True`` if the node represents a known directive, | ||||
|                 ``False`` otherwise | ||||
|         """ | ||||
|         return (isinstance(node, ast.Expr) and | ||||
|                 node.value and isinstance(node.value, ast.Call) and | ||||
|                 isinstance(node.value.func, ast.Name) and | ||||
|                 node.value.func.id in spack.directives.directive_names) | ||||
|     def visit_For(self, node): | ||||
|         self.generic_visit(node)            # visit children | ||||
|         return node if node.body else None  # remove loop if it has no body | ||||
| 
 | ||||
|     def is_spack_attr(self, node): | ||||
|         return (isinstance(node, ast.Assign) and | ||||
|                 node.targets and isinstance(node.targets[0], ast.Name) and | ||||
|                 node.targets[0].id in spack.package.Package.metadata_attrs) | ||||
|     def visit_While(self, node): | ||||
|         self.generic_visit(node)            # visit children | ||||
|         return node if node.body else None  # remove loop if it has no body | ||||
| 
 | ||||
|     def visit_If(self, node): | ||||
|         self.generic_visit(node) | ||||
| 
 | ||||
|         # an empty orelse is ignored by unparsing, but an empty body with a full orelse | ||||
|         # ends up unparsing as a syntax error, so we replace the empty body into `pass`. | ||||
|         if not node.body: | ||||
|             if node.orelse: | ||||
|                 node.body = [ast.Pass()] | ||||
|             else: | ||||
|                 return None | ||||
| 
 | ||||
|         # if the node has a body, it's valid python code with or without an orelse | ||||
|         return node | ||||
| 
 | ||||
|     def visit_FunctionDef(self, node): | ||||
|         # do not descend into function definitions | ||||
|         return node | ||||
| 
 | ||||
|     def visit_ClassDef(self, node): | ||||
|         # packages are always top-level, and we do not descend | ||||
|         # into nested class defs and their attributes | ||||
|         if self.in_classdef: | ||||
|             return node | ||||
| 
 | ||||
|         # guard against recrusive class definitions | ||||
|         self.in_classdef = True | ||||
|         self.generic_visit(node) | ||||
|         self.in_classdef = False | ||||
| 
 | ||||
|         # replace class definition with `pass` if it's empty (e.g., packages that only | ||||
|         # have directives b/c they subclass a build system class) | ||||
|         if not node.body: | ||||
|             node.body = [ast.Pass()] | ||||
| 
 | ||||
|     def visit_ClassDef(self, node):  # noqa | ||||
|         if node.name == spack.util.naming.mod_to_class(self.spec.name): | ||||
|             node.body = [ | ||||
|                 c for c in node.body | ||||
|                 if (not self.is_directive(c) and not self.is_spack_attr(c))] | ||||
|         return node | ||||
| 
 | ||||
| 
 | ||||
| @@ -89,7 +144,7 @@ def __init__(self, spec): | ||||
|         # map from function name to (implementation, condition_list) tuples | ||||
|         self.methods = {} | ||||
| 
 | ||||
|     def visit_FunctionDef(self, func):  # noqa | ||||
|     def visit_FunctionDef(self, func): | ||||
|         conditions = [] | ||||
|         for dec in func.decorator_list: | ||||
|             if isinstance(dec, ast.Call) and dec.func.id == 'when': | ||||
| @@ -207,7 +262,7 @@ def resolve(self, impl_conditions): | ||||
|         # if nothing was picked, the last definition wins. | ||||
|         return result | ||||
| 
 | ||||
|     def visit_FunctionDef(self, func):  # noqa | ||||
|     def visit_FunctionDef(self, func): | ||||
|         # if the function def wasn't visited on the first traversal there is a problem | ||||
|         assert func.name in self.methods, "Inconsistent package traversal!" | ||||
| 
 | ||||
| @@ -277,7 +332,7 @@ def package_ast(spec, filter_multimethods=True, source=None): | ||||
|     # create an AST | ||||
|     root = ast.parse(source) | ||||
| 
 | ||||
|     # remove docstrings and directives from the package AST | ||||
|     # remove docstrings, comments, and directives from the package AST | ||||
|     root = RemoveDocstrings().visit(root) | ||||
|     root = RemoveDirectives(spec).visit(root) | ||||
| 
 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user