# -*- python -*- # Copyright 2013-2024 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. ``mfem`` was chosen because it's one of the most complex packages in Spack, because it uses ``@when`` functions, because it has ``configure()`` calls with star-args in different locations, and beacuse it has a function with embedded unicode that needs to be unparsed consistently between Python versions. """ import os import shutil import sys from spack import * class Mfem(Package, CudaPackage, ROCmPackage): """Free, lightweight, scalable C++ library for finite element methods.""" tags = ['fem', 'finite-elements', 'high-order', 'amr', 'hpc', 'radiuss', 'e4s'] homepage = 'http://www.mfem.org' git = 'https://github.com/mfem/mfem.git' maintainers('v-dobrev', 'tzanio', 'acfisher', 'goxberry', 'markcmiller86') test_requires_compiler = True # Recommended mfem builds to test when updating this file: see the shell # script 'test_builds.sh' in the same directory as this file. # mfem is downloaded from a URL shortener at request of upstream # author Tzanio Kolev . See here: # https://github.com/mfem/mfem/issues/53 # # The following procedure should be used to verify security when a # new version is added: # # 1. Verify that no checksums on old versions have changed. # # 2. Verify that the shortened URL for the new version is listed at: # https://mfem.org/download/ # # 3. Use http://getlinkinfo.com or similar to verify that the # underling download link for the latest version comes has the # prefix: http://mfem.github.io/releases # # If this quick verification procedure fails, additional discussion # will be required to verify the new version. # 'develop' is a special version that is always larger (or newer) than any # other version. version('develop', branch='master') version('4.3.0', sha256='3a495602121b986049286ea0b23512279cdbdfb43c15c42a1511b521051fbe38', url='https://bit.ly/mfem-4-3', extension='tar.gz') version('4.2.0', '4352a225b55948d2e73a5ee88cece0e88bdbe7ba6726a23d68b2736d3221a86d', url='https://bit.ly/mfem-4-2', extension='tar.gz') version('4.1.0', '4c83fdcf083f8e2f5b37200a755db843cdb858811e25a8486ad36b2cbec0e11d', url='https://bit.ly/mfem-4-1', extension='tar.gz') # Tagged development version used by xSDK version('4.0.1-xsdk', commit='c55c80d17b82d80de04b849dd526e17044f8c99a') version('4.0.0', 'df5bdac798ea84a263979f6fbf79de9013e1c55562f95f98644c3edcacfbc727', url='https://bit.ly/mfem-4-0', extension='tar.gz') # Tagged development version used by the laghos package: version('3.4.1-laghos-v2.0', tag='laghos-v2.0') version('3.4.0', sha256='4e73e4fe0482636de3c5dc983cd395839a83cb16f6f509bd88b053e8b3858e05', url='https://bit.ly/mfem-3-4', extension='tar.gz') version('3.3.2', sha256='b70fa3c5080b9ec514fc05f4a04ff74322b99ac4ecd6d99c229f0ed5188fc0ce', url='https://goo.gl/Kd7Jk8', extension='tar.gz') # Tagged development version used by the laghos package: version('3.3.1-laghos-v1.0', tag='laghos-v1.0') version('3.3', sha256='b17bd452593aada93dc0fee748fcfbbf4f04ce3e7d77fdd0341cc9103bcacd0b', url='http://goo.gl/Vrpsns', extension='tar.gz') version('3.2', sha256='2938c3deed4ec4f7fd5b5f5cfe656845282e86e2dcd477d292390058b7b94340', url='http://goo.gl/Y9T75B', extension='tar.gz') version('3.1', sha256='841ea5cf58de6fae4de0f553b0e01ebaab9cd9c67fa821e8a715666ecf18fc57', url='http://goo.gl/xrScXn', extension='tar.gz') variant('static', default=True, description='Build static library') variant('shared', default=False, description='Build shared library') variant('mpi', default=True, description='Enable MPI parallelism') # Can we make the default value for 'metis' to depend on the 'mpi' value? variant('metis', default=True, description='Enable METIS support') variant('openmp', default=False, description='Enable OpenMP parallelism') # Note: '+cuda' and 'cuda_arch' variants are added by the CudaPackage # Note: '+rocm' and 'amdgpu_target' variants are added by the ROCmPackage variant('occa', default=False, description='Enable OCCA backend') variant('raja', default=False, description='Enable RAJA backend') variant('libceed', default=False, description='Enable libCEED backend') variant('umpire', default=False, description='Enable Umpire support') variant('amgx', default=False, description='Enable NVIDIA AmgX solver support') variant('threadsafe', default=False, description=('Enable thread safe features.' ' Required for OpenMP.' ' May cause minor performance issues.')) variant('superlu-dist', default=False, description='Enable MPI parallel, sparse direct solvers') variant('strumpack', default=False, description='Enable support for STRUMPACK') variant('suite-sparse', default=False, description='Enable serial, sparse direct solvers') variant('petsc', default=False, description='Enable PETSc solvers, preconditioners, etc.') variant('slepc', default=False, description='Enable SLEPc integration') variant('sundials', default=False, description='Enable Sundials time integrators') variant('pumi', default=False, description='Enable functionality based on PUMI') variant('gslib', default=False, description='Enable functionality based on GSLIB') variant('mpfr', default=False, description='Enable precise, 1D quadrature rules') variant('lapack', default=False, description='Use external blas/lapack routines') variant('debug', default=False, description='Build debug instead of optimized version') variant('netcdf', default=False, description='Enable Cubit/Genesis reader') variant('conduit', default=False, description='Enable binary data I/O using Conduit') variant('zlib', default=True, description='Support zip\'d streams for I/O') variant('gnutls', default=False, description='Enable secure sockets using GnuTLS') variant('libunwind', default=False, description='Enable backtrace on error support using Libunwind') # TODO: SIMD, Ginkgo, ADIOS2, HiOp, MKL CPardiso, Axom/Sidre variant('timer', default='auto', values=('auto', 'std', 'posix', 'mac', 'mpi'), description='Timing functions to use in mfem::StopWatch') variant('examples', default=False, description='Build and install examples') variant('miniapps', default=False, description='Build and install miniapps') conflicts('+shared', when='@:3.3.2') conflicts('~static~shared') conflicts('~threadsafe', when='@:3+openmp') conflicts('+cuda', when='@:3') conflicts('+rocm', when='@:4.1') conflicts('+cuda+rocm') conflicts('+netcdf', when='@:3.1') conflicts('+superlu-dist', when='@:3.1') # STRUMPACK support was added in mfem v3.3.2, however, here we allow only # strumpack v3+ support for which is available starting with mfem v4.0: conflicts('+strumpack', when='@:3') conflicts('+gnutls', when='@:3.1') conflicts('+zlib', when='@:3.2') conflicts('+mpfr', when='@:3.2') conflicts('+petsc', when='@:3.2') conflicts('+slepc', when='@:4.1') conflicts('+sundials', when='@:3.2') conflicts('+pumi', when='@:3.3.2') conflicts('+gslib', when='@:4.0') conflicts('timer=mac', when='@:3.3.0') conflicts('timer=mpi', when='@:3.3.0') conflicts('~metis+mpi', when='@:3.3.0') conflicts('+metis~mpi', when='@:3.3.0') conflicts('+conduit', when='@:3.3.2') conflicts('+occa', when='mfem@:3') conflicts('+raja', when='mfem@:3') conflicts('+libceed', when='mfem@:4.0') conflicts('+umpire', when='mfem@:4.0') conflicts('+amgx', when='mfem@:4.1') conflicts('+amgx', when='~cuda') conflicts('+mpi~cuda ^hypre+cuda') conflicts('+superlu-dist', when='~mpi') conflicts('+strumpack', when='~mpi') conflicts('+petsc', when='~mpi') conflicts('+slepc', when='~petsc') conflicts('+pumi', when='~mpi') conflicts('timer=mpi', when='~mpi') depends_on('mpi', when='+mpi') depends_on('hypre@2.10.0:2.13', when='@:3.3+mpi') depends_on('hypre@:2.20.0', when='@3.4:4.2+mpi') depends_on('hypre@:2.23.0', when='@4.3.0+mpi') depends_on('hypre', when='+mpi') depends_on('metis', when='+metis') depends_on('blas', when='+lapack') depends_on('lapack@3.0:', when='+lapack') depends_on('sundials@2.7.0', when='@:3.3.0+sundials~mpi') depends_on('sundials@2.7.0+mpi+hypre', when='@:3.3.0+sundials+mpi') depends_on('sundials@2.7.0:', when='@3.3.2:+sundials~mpi') depends_on('sundials@2.7.0:+mpi+hypre', when='@3.3.2:+sundials+mpi') depends_on('sundials@5.0.0:', when='@4.0.1-xsdk:+sundials~mpi') depends_on('sundials@5.0.0:+mpi+hypre', when='@4.0.1-xsdk:+sundials+mpi') for sm_ in CudaPackage.cuda_arch_values: depends_on('sundials@5.4.0:+cuda cuda_arch={0}'.format(sm_), when='@4.2.0:+sundials+cuda cuda_arch={0}'.format(sm_)) depends_on('pumi@2.2.3:', when='@4.2.0:+pumi') depends_on('pumi', when='+pumi~shared') depends_on('pumi+shared', when='+pumi+shared') depends_on('gslib@1.0.5:+mpi', when='+gslib+mpi') depends_on('gslib@1.0.5:~mpi~mpiio', when='+gslib~mpi') depends_on('suite-sparse', when='+suite-sparse') depends_on('superlu-dist', when='+superlu-dist') depends_on('strumpack@3.0.0:', when='+strumpack~shared') depends_on('strumpack@3.0.0:+shared', when='+strumpack+shared') for sm_ in CudaPackage.cuda_arch_values: depends_on('strumpack+cuda cuda_arch={0}'.format(sm_), when='+strumpack+cuda cuda_arch={0}'.format(sm_)) # The PETSc tests in MFEM will fail if PETSc is not configured with # SuiteSparse and MUMPS. On the other hand, if we require the variants # '+suite-sparse+mumps' of PETSc, the xsdk package concretization fails. depends_on('petsc@3.8:+mpi+double+hypre', when='+petsc') depends_on('slepc@3.8.0:', when='+slepc') # Recommended when building outside of xsdk: # depends_on('petsc@3.8:+mpi+double+hypre+suite-sparse+mumps', # when='+petsc') depends_on('mpfr', when='+mpfr') depends_on('netcdf-c@4.1.3:', when='+netcdf') depends_on('unwind', when='+libunwind') depends_on('zlib', when='+zlib') depends_on('gnutls', when='+gnutls') depends_on('conduit@0.3.1:,master:', when='+conduit') depends_on('conduit+mpi', when='+conduit+mpi') # The MFEM 4.0.0 SuperLU interface fails when using hypre@2.16.0 and # superlu-dist@6.1.1. See https://github.com/mfem/mfem/issues/983. # This issue was resolved in v4.1. conflicts('+superlu-dist', when='mfem@:4.0 ^hypre@2.16.0: ^superlu-dist@6:') # The STRUMPACK v3 interface in MFEM seems to be broken as of MFEM v4.1 # when using hypre version >= 2.16.0. # This issue is resolved in v4.2. conflicts('+strumpack', when='mfem@4.0.0:4.1 ^hypre@2.16.0:') conflicts('+strumpack ^strumpack+cuda', when='~cuda') depends_on('occa@1.0.8:', when='@:4.1+occa') depends_on('occa@1.1.0:', when='@4.2.0:+occa') depends_on('occa+cuda', when='+occa+cuda') # TODO: propagate '+rocm' variant to occa when it is supported depends_on('raja@0.10.0:', when='@4.0.1:+raja') depends_on('raja@0.7.0:0.9.0', when='@4.0.0+raja') for sm_ in CudaPackage.cuda_arch_values: depends_on('raja+cuda cuda_arch={0}'.format(sm_), when='+raja+cuda cuda_arch={0}'.format(sm_)) for gfx in ROCmPackage.amdgpu_targets: depends_on('raja+rocm amdgpu_target={0}'.format(gfx), when='+raja+rocm amdgpu_target={0}'.format(gfx)) depends_on('libceed@0.6:', when='@:4.1+libceed') depends_on('libceed@0.7:', when='@4.2.0:+libceed') for sm_ in CudaPackage.cuda_arch_values: depends_on('libceed+cuda cuda_arch={0}'.format(sm_), when='+libceed+cuda cuda_arch={0}'.format(sm_)) for gfx in ROCmPackage.amdgpu_targets: depends_on('libceed+rocm amdgpu_target={0}'.format(gfx), when='+libceed+rocm amdgpu_target={0}'.format(gfx)) depends_on('umpire@2.0.0:', when='+umpire') for sm_ in CudaPackage.cuda_arch_values: depends_on('umpire+cuda cuda_arch={0}'.format(sm_), when='+umpire+cuda cuda_arch={0}'.format(sm_)) for gfx in ROCmPackage.amdgpu_targets: depends_on('umpire+rocm amdgpu_target={0}'.format(gfx), when='+umpire+rocm amdgpu_target={0}'.format(gfx)) # AmgX: propagate the cuda_arch and mpi settings: for sm_ in CudaPackage.cuda_arch_values: depends_on('amgx+mpi cuda_arch={0}'.format(sm_), when='+amgx+mpi cuda_arch={0}'.format(sm_)) depends_on('amgx~mpi cuda_arch={0}'.format(sm_), when='+amgx~mpi cuda_arch={0}'.format(sm_)) patch('mfem_ppc_build.patch', when='@3.2:3.3.0 arch=ppc64le') patch('mfem-3.4.patch', when='@3.4.0') patch('mfem-3.3-3.4-petsc-3.9.patch', when='@3.3.0:3.4.0 +petsc ^petsc@3.9.0:') patch('mfem-4.2-umpire.patch', when='@4.2.0+umpire') patch('mfem-4.2-slepc.patch', when='@4.2.0+slepc') patch('mfem-4.2-petsc-3.15.0.patch', when='@4.2.0+petsc ^petsc@3.15.0:') patch('mfem-4.3-hypre-2.23.0.patch', when='@4.3.0') patch('mfem-4.3-cusparse-11.4.patch', when='@4.3.0+cuda') # Patch to fix MFEM makefile syntax error. See # https://github.com/mfem/mfem/issues/1042 for the bug report and # https://github.com/mfem/mfem/pull/1043 for the bugfix contributed # upstream. patch('mfem-4.0.0-makefile-syntax-fix.patch', when='@4.0.0') phases = ['configure', 'build', 'install'] def setup_build_environment(self, env): env.unset('MFEM_DIR') env.unset('MFEM_BUILD_DIR') # # Note: Although MFEM does support CMake configuration, MFEM # development team indicates that vanilla GNU Make is the # preferred mode of configuration of MFEM and the mode most # likely to be up to date in supporting *all* of MFEM's # configuration options. So, don't use CMake # def configure(self, spec, prefix): def yes_no(varstr): return 'YES' if varstr in self.spec else 'NO' # See also find_system_libraries in lib/spack/llnl/util/filesystem.py # where the same list of paths is used. sys_lib_paths = [ '/lib64', '/lib', '/usr/lib64', '/usr/lib', '/usr/local/lib64', '/usr/local/lib'] def is_sys_lib_path(dir): return dir in sys_lib_paths xcompiler = '' xlinker = '-Wl,' if '+cuda' in spec: xcompiler = '-Xcompiler=' xlinker = '-Xlinker=' cuda_arch = None if '~cuda' in spec else spec.variants['cuda_arch'].value # We need to add rpaths explicitly to allow proper export of link flags # from within MFEM. # Similar to spec[pkg].libs.ld_flags but prepends rpath flags too. # Also does not add system library paths as defined by 'sys_lib_paths' # above -- this is done to avoid issues like this: # https://github.com/mfem/mfem/issues/1088. def ld_flags_from_library_list(libs_list): flags = ['%s-rpath,%s' % (xlinker, dir) for dir in libs_list.directories if not is_sys_lib_path(dir)] flags += ['-L%s' % dir for dir in libs_list.directories if not is_sys_lib_path(dir)] flags += [libs_list.link_flags] return ' '.join(flags) def ld_flags_from_dirs(pkg_dirs_list, pkg_libs_list): flags = ['%s-rpath,%s' % (xlinker, dir) for dir in pkg_dirs_list if not is_sys_lib_path(dir)] flags += ['-L%s' % dir for dir in pkg_dirs_list if not is_sys_lib_path(dir)] flags += ['-l%s' % lib for lib in pkg_libs_list] return ' '.join(flags) def find_optional_library(name, prefix): for shared in [True, False]: for path in ['lib64', 'lib']: lib = find_libraries(name, join_path(prefix, path), shared=shared, recursive=False) if lib: return lib return LibraryList([]) # Determine how to run MPI tests, e.g. when using '--test=root', when # Spack is run inside a batch system job. mfem_mpiexec = 'mpirun' mfem_mpiexec_np = '-np' if 'SLURM_JOBID' in os.environ: mfem_mpiexec = 'srun' mfem_mpiexec_np = '-n' elif 'LSB_JOBID' in os.environ: if 'LLNL_COMPUTE_NODES' in os.environ: mfem_mpiexec = 'lrun' mfem_mpiexec_np = '-n' else: mfem_mpiexec = 'jsrun' mfem_mpiexec_np = '-p' metis5_str = 'NO' if ('+metis' in spec) and spec['metis'].satisfies('@5:'): metis5_str = 'YES' zlib_var = 'MFEM_USE_ZLIB' if (spec.satisfies('@4.1.0:')) else \ 'MFEM_USE_GZSTREAM' options = [ 'PREFIX=%s' % prefix, 'MFEM_USE_MEMALLOC=YES', 'MFEM_DEBUG=%s' % yes_no('+debug'), # NOTE: env['CXX'] is the spack c++ compiler wrapper. The real # compiler is defined by env['SPACK_CXX']. 'CXX=%s' % env['CXX'], 'MFEM_USE_LIBUNWIND=%s' % yes_no('+libunwind'), '%s=%s' % (zlib_var, yes_no('+zlib')), 'MFEM_USE_METIS=%s' % yes_no('+metis'), 'MFEM_USE_METIS_5=%s' % metis5_str, 'MFEM_THREAD_SAFE=%s' % yes_no('+threadsafe'), 'MFEM_USE_MPI=%s' % yes_no('+mpi'), 'MFEM_USE_LAPACK=%s' % yes_no('+lapack'), 'MFEM_USE_SUPERLU=%s' % yes_no('+superlu-dist'), 'MFEM_USE_STRUMPACK=%s' % yes_no('+strumpack'), 'MFEM_USE_SUITESPARSE=%s' % yes_no('+suite-sparse'), 'MFEM_USE_SUNDIALS=%s' % yes_no('+sundials'), 'MFEM_USE_PETSC=%s' % yes_no('+petsc'), 'MFEM_USE_SLEPC=%s' % yes_no('+slepc'), 'MFEM_USE_PUMI=%s' % yes_no('+pumi'), 'MFEM_USE_GSLIB=%s' % yes_no('+gslib'), 'MFEM_USE_NETCDF=%s' % yes_no('+netcdf'), 'MFEM_USE_MPFR=%s' % yes_no('+mpfr'), 'MFEM_USE_GNUTLS=%s' % yes_no('+gnutls'), 'MFEM_USE_OPENMP=%s' % yes_no('+openmp'), 'MFEM_USE_CONDUIT=%s' % yes_no('+conduit'), 'MFEM_USE_CUDA=%s' % yes_no('+cuda'), 'MFEM_USE_HIP=%s' % yes_no('+rocm'), 'MFEM_USE_OCCA=%s' % yes_no('+occa'), 'MFEM_USE_RAJA=%s' % yes_no('+raja'), 'MFEM_USE_AMGX=%s' % yes_no('+amgx'), 'MFEM_USE_CEED=%s' % yes_no('+libceed'), 'MFEM_USE_UMPIRE=%s' % yes_no('+umpire'), 'MFEM_MPIEXEC=%s' % mfem_mpiexec, 'MFEM_MPIEXEC_NP=%s' % mfem_mpiexec_np] cxxflags = spec.compiler_flags['cxxflags'] if cxxflags: # Add opt/debug flags if they are not present in global cxx flags opt_flag_found = any(f in self.compiler.opt_flags for f in cxxflags) debug_flag_found = any(f in self.compiler.debug_flags for f in cxxflags) if '+debug' in spec: if not debug_flag_found: cxxflags.append('-g') if not opt_flag_found: cxxflags.append('-O0') else: if not opt_flag_found: cxxflags.append('-O2') cxxflags = [(xcompiler + flag) for flag in cxxflags] if '+cuda' in spec: cxxflags += [ '-x=cu --expt-extended-lambda -arch=sm_%s' % cuda_arch, '-ccbin %s' % (spec['mpi'].mpicxx if '+mpi' in spec else env['CXX'])] if self.spec.satisfies('@4.0.0:'): cxxflags.append(self.compiler.cxx11_flag) # The cxxflags are set by the spack c++ compiler wrapper. We also # set CXXFLAGS explicitly, for clarity, and to properly export the # cxxflags in the variable MFEM_CXXFLAGS in config.mk. options += ['CXXFLAGS=%s' % ' '.join(cxxflags)] if '~static' in spec: options += ['STATIC=NO'] if '+shared' in spec: options += [ 'SHARED=YES', 'PICFLAG=%s' % (xcompiler + self.compiler.cxx_pic_flag)] if '+mpi' in spec: options += ['MPICXX=%s' % spec['mpi'].mpicxx] hypre = spec['hypre'] # The hypre package always links with 'blas' and 'lapack'. all_hypre_libs = hypre.libs + hypre['lapack'].libs + \ hypre['blas'].libs options += [ 'HYPRE_OPT=-I%s' % hypre.prefix.include, 'HYPRE_LIB=%s' % ld_flags_from_library_list(all_hypre_libs)] if '+metis' in spec: options += [ 'METIS_OPT=-I%s' % spec['metis'].prefix.include, 'METIS_LIB=%s' % ld_flags_from_library_list(spec['metis'].libs)] if '+lapack' in spec: lapack_blas = spec['lapack'].libs + spec['blas'].libs options += [ # LAPACK_OPT is not used 'LAPACK_LIB=%s' % ld_flags_from_library_list(lapack_blas)] if '+superlu-dist' in spec: lapack_blas = spec['lapack'].libs + spec['blas'].libs options += [ 'SUPERLU_OPT=-I%s -I%s' % (spec['superlu-dist'].prefix.include, spec['parmetis'].prefix.include), 'SUPERLU_LIB=%s %s' % (ld_flags_from_dirs([spec['superlu-dist'].prefix.lib, spec['parmetis'].prefix.lib], ['superlu_dist', 'parmetis']), ld_flags_from_library_list(lapack_blas))] if '+strumpack' in spec: strumpack = spec['strumpack'] sp_opt = ['-I%s' % strumpack.prefix.include] sp_lib = [ld_flags_from_library_list(strumpack.libs)] # Parts of STRUMPACK use fortran, so we need to link with the # fortran library and also the MPI fortran library: if '~shared' in strumpack: if os.path.basename(env['FC']) == 'gfortran': gfortran = Executable(env['FC']) libext = 'dylib' if sys.platform == 'darwin' else 'so' libfile = os.path.abspath(gfortran( '-print-file-name=libgfortran.%s' % libext, output=str).strip()) gfortran_lib = LibraryList(libfile) sp_lib += [ld_flags_from_library_list(gfortran_lib)] if ('^mpich' in strumpack) or ('^mvapich2' in strumpack): sp_lib += ['-lmpifort'] elif '^openmpi' in strumpack: sp_lib += ['-lmpi_mpifh'] elif '^spectrum-mpi' in strumpack: sp_lib += ['-lmpi_ibm_mpifh'] if '+openmp' in strumpack: # The '+openmp' in the spec means strumpack will TRY to find # OpenMP; if not found, we should not add any flags -- how do # we figure out if strumpack found OpenMP? if not self.spec.satisfies('%apple-clang'): sp_opt += [xcompiler + self.compiler.openmp_flag] if '^parmetis' in strumpack: parmetis = strumpack['parmetis'] sp_opt += [parmetis.headers.cpp_flags] sp_lib += [ld_flags_from_library_list(parmetis.libs)] if '^netlib-scalapack' in strumpack: scalapack = strumpack['scalapack'] sp_opt += ['-I%s' % scalapack.prefix.include] sp_lib += [ld_flags_from_dirs([scalapack.prefix.lib], ['scalapack'])] elif '^scalapack' in strumpack: scalapack = strumpack['scalapack'] sp_opt += [scalapack.headers.cpp_flags] sp_lib += [ld_flags_from_library_list(scalapack.libs)] if '+butterflypack' in strumpack: bp = strumpack['butterflypack'] sp_opt += ['-I%s' % bp.prefix.include] sp_lib += [ld_flags_from_dirs([bp.prefix.lib], ['dbutterflypack', 'zbutterflypack'])] if '+zfp' in strumpack: zfp = strumpack['zfp'] sp_opt += ['-I%s' % zfp.prefix.include] sp_lib += [ld_flags_from_dirs([zfp.prefix.lib], ['zfp'])] if '+cuda' in strumpack: # assuming also ('+cuda' in spec) sp_lib += ['-lcusolver', '-lcublas'] options += [ 'STRUMPACK_OPT=%s' % ' '.join(sp_opt), 'STRUMPACK_LIB=%s' % ' '.join(sp_lib)] if '+suite-sparse' in spec: ss_spec = 'suite-sparse:' + self.suitesparse_components options += [ 'SUITESPARSE_OPT=-I%s' % spec[ss_spec].prefix.include, 'SUITESPARSE_LIB=%s' % ld_flags_from_library_list(spec[ss_spec].libs)] if '+sundials' in spec: sun_spec = 'sundials:' + self.sundials_components options += [ 'SUNDIALS_OPT=%s' % spec[sun_spec].headers.cpp_flags, 'SUNDIALS_LIB=%s' % ld_flags_from_library_list(spec[sun_spec].libs)] if '+petsc' in spec: petsc = spec['petsc'] if '+shared' in petsc: options += [ 'PETSC_OPT=%s' % petsc.headers.cpp_flags, 'PETSC_LIB=%s' % ld_flags_from_library_list(petsc.libs)] else: options += ['PETSC_DIR=%s' % petsc.prefix] if '+slepc' in spec: slepc = spec['slepc'] options += [ 'SLEPC_OPT=%s' % slepc.headers.cpp_flags, 'SLEPC_LIB=%s' % ld_flags_from_library_list(slepc.libs)] if '+pumi' in spec: pumi_libs = ['pumi', 'crv', 'ma', 'mds', 'apf', 'pcu', 'gmi', 'parma', 'lion', 'mth', 'apf_zoltan', 'spr'] options += [ 'PUMI_OPT=-I%s' % spec['pumi'].prefix.include, 'PUMI_LIB=%s' % ld_flags_from_dirs([spec['pumi'].prefix.lib], pumi_libs)] if '+gslib' in spec: options += [ 'GSLIB_OPT=-I%s' % spec['gslib'].prefix.include, 'GSLIB_LIB=%s' % ld_flags_from_dirs([spec['gslib'].prefix.lib], ['gs'])] if '+netcdf' in spec: lib_flags = ld_flags_from_dirs([spec['netcdf-c'].prefix.lib], ['netcdf']) hdf5 = spec['hdf5:hl'] if hdf5.satisfies('~shared'): hdf5_libs = hdf5.libs hdf5_libs += LibraryList(find_system_libraries('libdl')) lib_flags += " " + ld_flags_from_library_list(hdf5_libs) options += [ 'NETCDF_OPT=-I%s' % spec['netcdf-c'].prefix.include, 'NETCDF_LIB=%s' % lib_flags] if '+zlib' in spec: if "@:3.3.2" in spec: options += ['ZLIB_DIR=%s' % spec['zlib'].prefix] else: options += [ 'ZLIB_OPT=-I%s' % spec['zlib'].prefix.include, 'ZLIB_LIB=%s' % ld_flags_from_library_list(spec['zlib'].libs)] if '+mpfr' in spec: options += [ 'MPFR_OPT=-I%s' % spec['mpfr'].prefix.include, 'MPFR_LIB=%s' % ld_flags_from_dirs([spec['mpfr'].prefix.lib], ['mpfr'])] if '+gnutls' in spec: options += [ 'GNUTLS_OPT=-I%s' % spec['gnutls'].prefix.include, 'GNUTLS_LIB=%s' % ld_flags_from_dirs([spec['gnutls'].prefix.lib], ['gnutls'])] if '+libunwind' in spec: libunwind = spec['unwind'] headers = find_headers('libunwind', libunwind.prefix.include) headers.add_macro('-g') libs = find_optional_library('libunwind', libunwind.prefix) # When mfem uses libunwind, it also needs 'libdl'. libs += LibraryList(find_system_libraries('libdl')) options += [ 'LIBUNWIND_OPT=%s' % headers.cpp_flags, 'LIBUNWIND_LIB=%s' % ld_flags_from_library_list(libs)] if '+openmp' in spec: options += [ 'OPENMP_OPT=%s' % (xcompiler + self.compiler.openmp_flag)] if '+cuda' in spec: options += [ 'CUDA_CXX=%s' % join_path(spec['cuda'].prefix, 'bin', 'nvcc'), 'CUDA_ARCH=sm_%s' % cuda_arch] if '+rocm' in spec: amdgpu_target = ','.join(spec.variants['amdgpu_target'].value) options += [ 'HIP_CXX=%s' % spec['hip'].hipcc, 'HIP_ARCH=%s' % amdgpu_target] if '+occa' in spec: options += ['OCCA_OPT=-I%s' % spec['occa'].prefix.include, 'OCCA_LIB=%s' % ld_flags_from_dirs([spec['occa'].prefix.lib], ['occa'])] if '+raja' in spec: options += ['RAJA_OPT=-I%s' % spec['raja'].prefix.include, 'RAJA_LIB=%s' % ld_flags_from_dirs([spec['raja'].prefix.lib], ['RAJA'])] if '+amgx' in spec: amgx = spec['amgx'] if '+shared' in amgx: options += ['AMGX_OPT=-I%s' % amgx.prefix.include, 'AMGX_LIB=%s' % ld_flags_from_library_list(amgx.libs)] else: options += ['AMGX_DIR=%s' % amgx.prefix] if '+libceed' in spec: options += ['CEED_OPT=-I%s' % spec['libceed'].prefix.include, 'CEED_LIB=%s' % ld_flags_from_dirs([spec['libceed'].prefix.lib], ['ceed'])] if '+umpire' in spec: options += ['UMPIRE_OPT=-I%s' % spec['umpire'].prefix.include, 'UMPIRE_LIB=%s' % ld_flags_from_library_list(spec['umpire'].libs)] timer_ids = {'std': '0', 'posix': '2', 'mac': '4', 'mpi': '6'} timer = spec.variants['timer'].value if timer != 'auto': options += ['MFEM_TIMER_TYPE=%s' % timer_ids[timer]] if '+conduit' in spec: conduit = spec['conduit'] headers = HeaderList(find(conduit.prefix.include, 'conduit.hpp', recursive=True)) conduit_libs = ['libconduit', 'libconduit_relay', 'libconduit_blueprint'] libs = find_libraries(conduit_libs, conduit.prefix.lib, shared=('+shared' in conduit)) libs += LibraryList(find_system_libraries('libdl')) if '+hdf5' in conduit: hdf5 = conduit['hdf5'] headers += find_headers('hdf5', hdf5.prefix.include) libs += hdf5.libs ################## # cyrush note: ################## # spack's HeaderList is applying too much magic, undermining us: # # It applies a regex to strip back to the last "include" dir # in the path. In our case we need to pass the following # as part of the CONDUIT_OPT flags: # # -I/include/conduit # # I tried several ways to present this path to the HeaderList, # but the regex always kills the trailing conduit dir # breaking build. # # To resolve the issue, we simply join our own string with # the headers results (which are important b/c they handle # hdf5 paths when enabled). ################## # construct proper include path conduit_include_path = conduit.prefix.include.conduit # add this path to the found flags conduit_opt_flags = "-I{0} {1}".format(conduit_include_path, headers.cpp_flags) options += [ 'CONDUIT_OPT=%s' % conduit_opt_flags, 'CONDUIT_LIB=%s' % ld_flags_from_library_list(libs)] make('config', *options, parallel=False) make('info', parallel=False) def build(self, spec, prefix): make('lib') @run_after('build') def check_or_test(self): # Running 'make check' or 'make test' may fail if MFEM_MPIEXEC or # MFEM_MPIEXEC_NP are not set appropriately. if not self.run_tests: # check we can build ex1 (~mpi) or ex1p (+mpi). make('-C', 'examples', 'ex1p' if ('+mpi' in self.spec) else 'ex1', parallel=False) # make('check', parallel=False) else: make('all') make('test', parallel=False) def install(self, spec, prefix): make('install', parallel=False) # TODO: The way the examples and miniapps are being installed is not # perfect. For example, the makefiles do not work. install_em = ('+examples' in spec) or ('+miniapps' in spec) if install_em and ('+shared' in spec): make('examples/clean', 'miniapps/clean') # This is a hack to get the examples and miniapps to link with the # installed shared mfem library: with working_dir('config'): os.rename('config.mk', 'config.mk.orig') copy(str(self.config_mk), 'config.mk') shutil.copystat('config.mk.orig', 'config.mk') prefix_share = join_path(prefix, 'share', 'mfem') if '+examples' in spec: make('examples') install_tree('examples', join_path(prefix_share, 'examples')) if '+miniapps' in spec: make('miniapps') install_tree('miniapps', join_path(prefix_share, 'miniapps')) if install_em: install_tree('data', join_path(prefix_share, 'data')) examples_src_dir = 'examples' examples_data_dir = 'data' @run_after('install') def cache_test_sources(self): """Copy the example source files after the package is installed to an install test subdirectory for use during `spack test run`.""" cache_extra_test_sources(self, [self.examples_src_dir, self.examples_data_dir]) def test(self): test_dir = join_path( self.test_suite.current_test_cache_dir, self.examples_src_dir ) # MFEM has many examples to serve as a suitable smoke check. ex10 # was chosen arbitrarily among the examples that work both with # MPI and without it test_exe = 'ex10p' if ('+mpi' in self.spec) else 'ex10' self.run_test( 'make', [ 'CONFIG_MK={0}/share/mfem/config.mk'.format(self.prefix), test_exe, 'parallel=False' ], purpose='test: building {0}'.format(test_exe), skip_missing=False, work_dir=test_dir, ) self.run_test( './{0}'.format(test_exe), [ '--mesh', '../{0}/beam-quad.mesh'.format(self.examples_data_dir) ], [], installed=False, purpose='test: running {0}'.format(test_exe), skip_missing=False, work_dir=test_dir, ) # this patch is only needed for mfem 4.1, where a few # released files include byte order marks @when('@4.1.0') def patch(self): # Remove the byte order mark since it messes with some compilers files_with_bom = [ 'fem/gslib.hpp', 'fem/gslib.cpp', 'linalg/hiop.hpp', 'miniapps/gslib/field-diff.cpp', 'miniapps/gslib/findpts.cpp', 'miniapps/gslib/pfindpts.cpp'] bom = '\xef\xbb\xbf' if sys.version_info < (3,) else u'\ufeff' for f in files_with_bom: filter_file(bom, '', f) @property def suitesparse_components(self): """Return the SuiteSparse components needed by MFEM.""" ss_comps = 'umfpack,cholmod,colamd,amd,camd,ccolamd,suitesparseconfig' if self.spec.satisfies('@3.2:'): ss_comps = 'klu,btf,' + ss_comps return ss_comps @property def sundials_components(self): """Return the SUNDIALS components needed by MFEM.""" spec = self.spec sun_comps = 'arkode,cvodes,nvecserial,kinsol' if '+mpi' in spec: if spec.satisfies('@4.2:'): sun_comps += ',nvecparallel,nvecmpiplusx' else: sun_comps += ',nvecparhyp,nvecparallel' if '+cuda' in spec and '+cuda' in spec['sundials']: sun_comps += ',nveccuda' return sun_comps @property def headers(self): """Export the main mfem header, mfem.hpp. """ hdrs = HeaderList(find(self.prefix.include, 'mfem.hpp', recursive=False)) return hdrs or None @property def libs(self): """Export the mfem library file. """ libs = find_libraries('libmfem', root=self.prefix.lib, shared=('+shared' in self.spec), recursive=False) return libs or None @property def config_mk(self): """Export the location of the config.mk file. This property can be accessed using spec['mfem'].package.config_mk """ dirs = [self.prefix, self.prefix.share.mfem] for d in dirs: f = join_path(d, 'config.mk') if os.access(f, os.R_OK): return FileList(f) return FileList(find(self.prefix, 'config.mk', recursive=True)) @property def test_mk(self): """Export the location of the test.mk file. This property can be accessed using spec['mfem'].package.test_mk. In version 3.3.2 and newer, the location of test.mk is also defined inside config.mk, variable MFEM_TEST_MK. """ dirs = [self.prefix, self.prefix.share.mfem] for d in dirs: f = join_path(d, 'test.mk') if os.access(f, os.R_OK): return FileList(f) return FileList(find(self.prefix, 'test.mk', recursive=True))