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:
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."""
|
||||
def __init__(self, spec):
|
||||
self.spec = spec
|
||||
"""Remove Spack directives from a package AST.
|
||||
|
||||
def is_directive(self, node):
|
||||
"""Check to determine if the node is a valid directive
|
||||
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.
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
Args:
|
||||
node (ast.AST): the AST node being checked
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if the node represents a known directive,
|
||||
``False`` otherwise
|
||||
"""
|
||||
return (isinstance(node, ast.Expr) and
|
||||
|
||||
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 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)
|
||||
node.value.func.id in spack.directives.directive_names
|
||||
) else node
|
||||
|
||||
def is_spack_attr(self, node):
|
||||
return (isinstance(node, ast.Assign) and
|
||||
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 spack.package.Package.metadata_attrs)
|
||||
node.targets[0].id in self.metadata_attrs
|
||||
) else node
|
||||
|
||||
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
|
||||
|
||||
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 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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user