feat(initial): added portable mfem build system

This commit is contained in:
2026-09-05 07:11:05 -04:00
commit 6a54f2625e
49 changed files with 4344 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
/dist/
/.mesonpy-*/
/.pytest_cache/
/.venv/
__pycache__/
*.egg-info/
*.so
*.dylib
*.dll
subprojects/packagecache/
subprojects/*/
!subprojects/packagefiles/

18
build-check/meson.build Normal file
View File

@@ -0,0 +1,18 @@
message('C compiler: ' + cc.get_id())
message('C++ compiler: ' + cpp.get_id())
if not cpp.has_header('memory') or not cpp.compiles('''
#include <type_traits>
int main() { static_assert(__cplusplus >= 201703L); return 0; }
''', name: 'C++17 language support')
error('MFEM 4.10 requires a C++17-or-newer compiler.')
endif
cmake_program = find_program('cmake', required: true)
# With meson-python, the generated native file pins this to the interpreter
# building the wheel. For command-line builds, PATH controls the selection.
python_build = python_mod.find_installation(required: true, pure: false)
if meson.is_cross_build()
message('Cross build: ' + build_machine.system() + ' -> ' + host_machine.system())
endif

2
build-config/meson.build Normal file
View File

@@ -0,0 +1,2 @@
subdir('mfem')

View File

@@ -0,0 +1,651 @@
profile = get_option('feature_profile')
allow_preinstalled = get_option('allow_preinstalled')
dependency_prefix = get_option('dependency_prefix')
wheel_carries_native_bundle = get_option('build_python') and get_option('python_install_native')
effective_allow_preinstalled = allow_preinstalled and not wheel_carries_native_bundle
is_wasm = host_machine.system() == 'emscripten'
is_windows = host_machine.system() == 'windows'
can_build_posix_mpi = not meson.is_cross_build() and not is_windows and not is_wasm
nvcc_program = find_program('nvcc', required: false)
hipcc_program = find_program('hipcc', required: false)
dependency_has_nvcc = dependency_prefix != '' and fs.exists(dependency_prefix / 'bin' / 'nvcc')
dependency_has_hipcc = dependency_prefix != '' and fs.exists(dependency_prefix / 'bin' / 'hipcc')
cuda_toolkit_available = nvcc_program.found() or dependency_has_nvcc
hip_toolkit_available = hipcc_program.found() or dependency_has_hipcc
openmp_dep = dependency('openmp', required: false)
bundled_compiler_openmp = cpp.get_id() == 'gcc' and openmp_dep.found()
usable_openmp = openmp_dep.found() and (
effective_allow_preinstalled or dependency_prefix != '' or bundled_compiler_openmp
)
simd_arch_supported = host_machine.cpu_family() in ['x86', 'x86_64']
if host_machine.cpu_family() in ['aarch64', 'arm']
simd_arch_supported = cpp.compiles('''
#ifndef __ARM_FEATURE_SVE
#error SVE is required by MFEM's ARM SIMD specialization
#endif
int main() { return 0; }
''', name: 'MFEM ARM SVE SIMD support')
elif host_machine.cpu_family() in ['ppc', 'ppc64']
simd_arch_supported = cpp.compiles('''
#ifndef __VSX__
#error VSX is required by MFEM's Power SIMD specialization
#endif
int main() { return 0; }
''', name: 'MFEM Power VSX SIMD support')
endif
mfem_feature_names = [
'mpi', 'metis', 'exceptions', 'zlib', 'libunwind', 'lapack',
'thread_safe', 'openmp', 'legacy_openmp', 'memalloc', 'sundials',
'suitesparse', 'superlu', 'superlu5', 'mumps', 'strumpack', 'cudss',
'ginkgo', 'amgx', 'magma', 'gnutls', 'gslib', 'hdf5', 'netcdf',
'petsc', 'slepc', 'mpfr', 'sidre', 'fms', 'conduit', 'pumi', 'hiop',
'cuda', 'hip', 'occa', 'raja', 'ceed', 'umpire', 'simd', 'adios2',
'caliper', 'algoim', 'mkl_cpardiso', 'mkl_pardiso', 'adforward',
'codipack', 'benchmark', 'parelag', 'tribol', 'enzyme', 'moonolith',
'simmetrix',
]
mfem_cmake_names = [
'MFEM_USE_MPI', 'MFEM_USE_METIS', 'MFEM_USE_EXCEPTIONS', 'MFEM_USE_ZLIB',
'MFEM_USE_LIBUNWIND', 'MFEM_USE_LAPACK', 'MFEM_THREAD_SAFE',
'MFEM_USE_OPENMP', 'MFEM_USE_LEGACY_OPENMP', 'MFEM_USE_MEMALLOC',
'MFEM_USE_SUNDIALS', 'MFEM_USE_SUITESPARSE', 'MFEM_USE_SUPERLU',
'MFEM_USE_SUPERLU5', 'MFEM_USE_MUMPS', 'MFEM_USE_STRUMPACK',
'MFEM_USE_CUDSS', 'MFEM_USE_GINKGO', 'MFEM_USE_AMGX', 'MFEM_USE_MAGMA',
'MFEM_USE_GNUTLS', 'MFEM_USE_GSLIB', 'MFEM_USE_HDF5', 'MFEM_USE_NETCDF',
'MFEM_USE_PETSC', 'MFEM_USE_SLEPC', 'MFEM_USE_MPFR', 'MFEM_USE_SIDRE',
'MFEM_USE_FMS', 'MFEM_USE_CONDUIT', 'MFEM_USE_PUMI', 'MFEM_USE_HIOP',
'MFEM_USE_CUDA', 'MFEM_USE_HIP', 'MFEM_USE_OCCA', 'MFEM_USE_RAJA',
'MFEM_USE_CEED', 'MFEM_USE_UMPIRE', 'MFEM_USE_SIMD', 'MFEM_USE_ADIOS2',
'MFEM_USE_CALIPER', 'MFEM_USE_ALGOIM', 'MFEM_USE_MKL_CPARDISO',
'MFEM_USE_MKL_PARDISO', 'MFEM_USE_ADFORWARD', 'MFEM_USE_CODIPACK',
'MFEM_USE_BENCHMARK', 'MFEM_USE_PARELAG', 'MFEM_USE_TRIBOL',
'MFEM_USE_ENZYME', 'MFEM_USE_MOONOLITH', 'MFEM_USE_SIMMETRIX',
]
source_capable = {
'mpi': can_build_posix_mpi,
'metis': not is_windows and not is_wasm,
'exceptions': true,
'zlib': true,
'thread_safe': true,
'openmp': bundled_compiler_openmp,
'legacy_openmp': bundled_compiler_openmp,
'memalloc': true,
'sundials': not is_windows and not is_wasm,
'gslib': not is_windows and not is_wasm,
'fms': not is_windows,
'ceed': not is_windows,
'algoim': not meson.is_cross_build() and not is_windows and not is_wasm,
'cuda': cuda_toolkit_available and not is_wasm and not host_machine.system() == 'darwin',
'hip': hip_toolkit_available and not is_wasm and not host_machine.system() == 'darwin',
'simd': simd_arch_supported and not is_wasm,
}
single_precision_conflicts = [
'sundials', 'suitesparse', 'superlu', 'strumpack', 'ginkgo', 'amgx',
'slepc', 'pumi', 'simmetrix', 'gslib', 'algoim', 'ceed', 'tribol', 'moonolith',
]
cuda_option = get_option('mfem_cuda')
hip_option = get_option('mfem_hip')
profile_cuda = false
profile_hip = false
if cuda_option.enabled() and hip_option.enabled()
profile_cuda = true
profile_hip = true
elif cuda_option.enabled()
profile_cuda = true
elif hip_option.enabled()
profile_hip = true
else
profile_cuda = cuda_option.auto() and source_capable.get('cuda')
profile_hip = hip_option.auto() and not profile_cuda and source_capable.get('hip')
endif
portable_defaults = {
'mpi': can_build_posix_mpi,
'metis': can_build_posix_mpi,
'exceptions': true,
'zlib': true,
'thread_safe': true,
'openmp': usable_openmp,
'memalloc': true,
'sundials': not is_windows and not is_wasm,
'gslib': not is_windows and not is_wasm,
'fms': not is_windows,
'ceed': not is_windows,
'algoim': source_capable.get('algoim'),
'cuda': profile_cuda,
'hip': profile_hip,
'simd': simd_arch_supported and not is_wasm,
}
full_cuda = false
full_hip = false
if profile == 'full'
full_cuda = profile_cuda
full_hip = profile_hip
endif
mfem_features = {}
mfem_feature_states = []
foreach feature_name : mfem_feature_names
feature_opt = get_option('mfem_' + feature_name)
feature_on = feature_opt.enabled()
if feature_opt.auto()
if profile == 'portable'
feature_on = portable_defaults.get(feature_name, false)
elif profile == 'full'
feature_on = true
if feature_name == 'legacy_openmp' or feature_name == 'superlu5'
feature_on = false
elif feature_name == 'openmp'
feature_on = usable_openmp
elif feature_name == 'cuda'
feature_on = full_cuda
elif feature_name == 'hip'
feature_on = full_hip
elif feature_name == 'cudss' or feature_name == 'amgx'
feature_on = full_cuda
elif feature_name == 'magma'
feature_on = full_cuda or full_hip
elif feature_name == 'simd'
feature_on = simd_arch_supported and not is_wasm
elif feature_name == 'enzyme'
feature_on = cpp.get_id() == 'clang' and not full_cuda
elif feature_name in ['benchmark', 'parelag', 'tribol']
feature_on = false
endif
endif
endif
if (
feature_opt.auto() and get_option('mfem_precision') == 'single' and
feature_name in single_precision_conflicts
)
feature_on = false
endif
if (
feature_on and
feature_name not in ['cuda', 'hip', 'openmp', 'simd'] and
not effective_allow_preinstalled and dependency_prefix == '' and
not source_capable.get(feature_name, false)
)
error(
'Selected feature mfem_' + feature_name + ' has no bundled source provider. ' +
'Supply -Ddependency_prefix=/path/to/a/hermetic/prefix or enable preinstalled dependencies.'
)
endif
mfem_features += {feature_name: feature_on}
mfem_feature_states += [feature_on]
endforeach
foreach build_only_feature : ['benchmark', 'parelag', 'tribol']
if get_option('mfem_' + build_only_feature).enabled()
error(
'mfem_' + build_only_feature + ' is an upstream non-installed ' +
'benchmark/miniapp build switch, not an MFEM library capability. ' +
'Build that program in a separate consumer project against this SDK.'
)
endif
endforeach
mfem_has_mpi = mfem_features.get('mpi')
mfem_has_metis = mfem_features.get('metis')
mfem_has_cuda = mfem_features.get('cuda')
mfem_has_hip = mfem_features.get('hip')
mfem_has_openmp = mfem_features.get('openmp')
mfem_has_gslib = mfem_features.get('gslib')
mfem_has_zlib = mfem_features.get('zlib')
mfem_has_sundials = mfem_features.get('sundials')
if get_option('mfem_mpi').enabled() and not can_build_posix_mpi and not allow_preinstalled and dependency_prefix == ''
error('The bundled MPI provider needs a native POSIX build. For cross/Windows builds, provide an MPI prefix or use -Dmfem_mpi=disabled.')
endif
if get_option('mfem_cuda').enabled() and not source_capable.get('cuda')
error('CUDA was forced on, but nvcc and a supported non-macOS CUDA host were not found.')
endif
if get_option('mfem_hip').enabled() and not source_capable.get('hip')
error('HIP was forced on, but hipcc was not found.')
endif
if get_option('mfem_openmp').enabled() and not usable_openmp
error('OpenMP was forced on, but no permitted OpenMP runtime was found for the selected compiler.')
endif
if get_option('mfem_simd').enabled() and not simd_arch_supported
error('SIMD was forced on, but MFEM has no intrinsic specialization for this target/compiler.')
endif
if get_option('mfem_enzyme').enabled() and cpp.get_id() != 'clang'
error('Enzyme was forced on, but Enzyme requires a matching Clang/LLVM compiler toolchain.')
endif
if mfem_has_cuda and mfem_has_hip
error('MFEM CUDA and HIP backends are mutually exclusive.')
endif
if mfem_features.get('enzyme') and mfem_has_cuda
error('The bundled CUDA route uses nvcc and cannot be combined with Enzyme; use a dedicated clang-CUDA toolchain or disable one feature.')
endif
if mfem_features.get('cudss') and not mfem_has_cuda
error('cuDSS requires -Dmfem_cuda=enabled (or an auto-detected CUDA toolkit).')
endif
if mfem_features.get('amgx') and not mfem_has_cuda
error('AmgX requires the CUDA backend.')
endif
if mfem_features.get('magma') and not (mfem_has_cuda or mfem_has_hip)
error('MAGMA requires either the CUDA or HIP backend.')
endif
if mfem_features.get('slepc') and not mfem_features.get('petsc')
error('SLEPc requires PETSc.')
endif
if mfem_features.get('adforward') and not mfem_features.get('codipack')
error('Forward-mode automatic differentiation requires CoDiPack.')
endif
if mfem_features.get('superlu5') and not mfem_features.get('superlu')
error('The SuperLU 5 compatibility option requires SuperLU_DIST.')
endif
if mfem_features.get('simmetrix') and not mfem_features.get('pumi')
error('Simmetrix integration requires PUMI.')
endif
foreach mpi_feature : ['superlu', 'mumps', 'strumpack', 'petsc', 'slepc', 'pumi', 'mkl_cpardiso', 'tribol']
if mfem_features.get(mpi_feature) and not mfem_has_mpi
error('MFEM feature ' + mpi_feature + ' requires MPI/Hypre.')
endif
endforeach
if mfem_features.get('legacy_openmp') and (
mfem_has_openmp or mfem_has_cuda or mfem_features.get('raja') or mfem_features.get('occa'))
error('Legacy OpenMP cannot be combined with OpenMP, CUDA, RAJA, or OCCA.')
endif
if mfem_features.get('legacy_openmp') and not mfem_features.get('thread_safe')
error('Legacy OpenMP requires MFEM thread-safe mode.')
endif
if get_option('mfem_precision') == 'single'
foreach single_conflict : single_precision_conflicts
if mfem_features.get(single_conflict)
error('Single precision is incompatible with MFEM feature ' + single_conflict + '.')
endif
endforeach
endif
mfem_consumer_cpp_std = (
mfem_features.get('raja') or mfem_features.get('umpire')
) ? 'c++20' : 'c++17'
system_mfem = disabler()
mfem_provider = 'source bundle'
if effective_allow_preinstalled
system_candidate = dependency('mfem', version: '>=4.10', required: false, allow_fallback: false)
if system_candidate.found()
system_compatible = true
foreach i : range(mfem_feature_names.length())
if mfem_feature_states[i]
feature_macro = mfem_cmake_names[i]
macro_ok = cpp.compiles(
'#include <mfem/config/config.hpp>\n#ifndef ' + feature_macro + '\n#error missing\n#endif\nint main(){return 0;}',
dependencies: system_candidate,
name: 'system MFEM provides ' + feature_macro,
)
system_compatible = system_compatible and macro_ok
endif
endforeach
precision_macro = get_option('mfem_precision') == 'single' ? 'MFEM_USE_SINGLE' : 'MFEM_USE_DOUBLE'
system_compatible = system_compatible and cpp.compiles(
'#include <mfem/config/config.hpp>\n#ifndef ' + precision_macro + '\n#error precision mismatch\n#endif\nint main(){return 0;}',
dependencies: system_candidate,
name: 'system MFEM uses requested precision',
)
foreach i : range(mfem_feature_names.length())
feature_opt = get_option('mfem_' + mfem_feature_names[i])
feature_macro = mfem_cmake_names[i]
if feature_opt.disabled()
macro_absent = cpp.compiles(
'#include <mfem/config/config.hpp>\n#ifdef ' + feature_macro + '\n#error explicitly disabled\n#endif\nint main(){return 0;}',
dependencies: system_candidate,
name: 'system MFEM omits disabled ' + feature_macro,
)
system_compatible = system_compatible and macro_absent
endif
endforeach
if system_compatible
system_mfem = system_candidate
mfem_provider = 'system'
else
message('The system MFEM does not satisfy the selected feature set; using the pinned source bundle.')
endif
endif
endif
uses_unmodelled_system_tpl = false
foreach feature_name : mfem_feature_names
if (
mfem_features.get(feature_name) and feature_name != 'openmp' and
not source_capable.get(feature_name, false)
)
uses_unmodelled_system_tpl = true
endif
endforeach
if (
not system_mfem.found() and effective_allow_preinstalled and
dependency_prefix == '' and uses_unmodelled_system_tpl
)
error(
'The selected features need external TPLs, but no complete compatible system MFEM was found. ' +
'Provide one coherent -Ddependency_prefix=/path/to/tpls so the source MFEM usage interface remains reproducible.'
)
endif
if not system_mfem.found() and is_windows
error(
'The source fallback currently targets POSIX and Emscripten toolchains. ' +
'On Windows, provide a complete compatible MFEM installation and use -Dallow_preinstalled=true.'
)
endif
mfem_runtime_prefix = ''
mfem_build_target = []
mpi_launcher_from_dependency = false
if system_mfem.found()
mfem_dep = system_mfem
else
mfem_source = subproject('mfem').get_variable('mfem_source_dir')
mpich_source = ''
hypre_source = ''
metis_source = ''
gslib_source = ''
zlib_source = ''
sundials_source = ''
libceed_source = ''
fms_source = ''
algoim_source = ''
blitz_source = ''
dependency_has_mpi = dependency_prefix != '' and (
fs.exists(dependency_prefix / 'bin' / 'mpicc') and
fs.exists(dependency_prefix / 'bin' / 'mpicxx')
)
dependency_has_hypre = dependency_prefix != '' and fs.exists(
dependency_prefix / 'include' / 'HYPRE_config.h'
)
dependency_has_metis = dependency_prefix != '' and fs.exists(
dependency_prefix / 'include' / 'metis.h'
)
dependency_has_gslib = dependency_prefix != '' and fs.exists(
dependency_prefix / 'include' / 'gslib' / 'gslib.h'
)
dependency_has_zlib = dependency_prefix != '' and fs.exists(
dependency_prefix / 'include' / 'zlib.h'
)
dependency_has_sundials = dependency_prefix != '' and fs.exists(
dependency_prefix / 'include' / 'sundials' / 'sundials_config.h'
)
dependency_has_ceed = dependency_prefix != '' and fs.exists(
dependency_prefix / 'include' / 'ceed.h'
)
dependency_has_fms = dependency_prefix != '' and fs.exists(
dependency_prefix / 'include' / 'fms.h'
)
dependency_has_algoim = dependency_prefix != '' and fs.exists(
dependency_prefix / 'include' / 'algoim_quad.hpp'
) and fs.exists(dependency_prefix / 'include' / 'blitz' / 'array.h')
build_mpi_from_source = mfem_has_mpi and can_build_posix_mpi and not dependency_has_mpi
mpi_launcher_from_dependency = mfem_has_mpi and dependency_has_mpi
if build_mpi_from_source
mpich_source = subproject('mpich').get_variable('mpich_source_dir')
endif
if mfem_has_mpi and (build_mpi_from_source or not dependency_has_hypre)
hypre_source = subproject('hypre').get_variable('hypre_source_dir')
endif
if mfem_has_metis and not dependency_has_metis
metis_source = subproject('metis').get_variable('metis_source_dir')
endif
if mfem_has_gslib and (build_mpi_from_source or not dependency_has_gslib)
gslib_source = subproject('gslib').get_variable('gslib_source_dir')
endif
if mfem_has_zlib and not dependency_has_zlib
zlib_source = subproject('zlib').get_variable('zlib_source_dir')
endif
if mfem_has_sundials and (build_mpi_from_source or not dependency_has_sundials)
sundials_source = subproject('sundials').get_variable('sundials_source_dir')
endif
if mfem_features.get('ceed') and not dependency_has_ceed
libceed_source = subproject('libceed').get_variable('libceed_source_dir')
endif
if mfem_features.get('fms') and not dependency_has_fms
fms_source = subproject('fms').get_variable('fms_source_dir')
endif
if mfem_features.get('algoim') and not dependency_has_algoim
algoim_source = subproject('algoim').get_variable('algoim_source_dir')
blitz_source = subproject('blitz').get_variable('blitz_source_dir')
endif
bundle_is_python_package = get_option('build_python') and get_option('python_install_native')
bundle_output_name = bundle_is_python_package ? 'meson_mfem_template' : 'mfem-prefix'
mfem_runtime_prefix = meson.current_build_dir() / bundle_output_name
bundle_work_dir = meson.current_build_dir() / '_bundle' / 'work'
bundle_builder = files('../../tools/build_mfem_bundle.py')
bundle_auxiliary_files = files('../../tools/ceed_jit_source_root_relocatable.c')
cc_command = cc.cmd_array()
cxx_command = cpp.cmd_array()
known_compiler_launchers = ['ccache', 'sccache', 'distcc']
cc_executable = ''
cc_launchers = []
cc_fixed_args = []
foreach command_part : cc_command
if cc_executable == '' and fs.name(command_part) in known_compiler_launchers
cc_launchers += [command_part]
elif cc_executable == ''
cc_executable = command_part
else
cc_fixed_args += [command_part]
endif
endforeach
cxx_executable = ''
cxx_launchers = []
cxx_fixed_args = []
foreach command_part : cxx_command
if cxx_executable == '' and fs.name(command_part) in known_compiler_launchers
cxx_launchers += [command_part]
elif cxx_executable == ''
cxx_executable = command_part
else
cxx_fixed_args += [command_part]
endif
endforeach
if cc_executable == '' or cxx_executable == ''
error('Could not identify the C/C++ compiler executables in Meson compiler commands.')
endif
bundle_command = [
python_build,
bundle_builder,
'--source', mfem_source,
'--work-dir', bundle_work_dir,
'--prefix', '@OUTPUT@',
'--cmake', cmake_program,
'--cc', cc_executable,
'--cxx', cxx_executable,
'--buildtype', get_option('buildtype'),
'--jobs', get_option('jobs').to_string(),
'--host-system', host_machine.system(),
'--cross-build', meson.is_cross_build() ? 'true' : 'false',
'--library-kind', is_wasm ? 'static' : 'shared',
'--cuda-arch', get_option('mfem_cuda_arch'),
'--hip-arch', get_option('mfem_hip_arch'),
'--precision', get_option('mfem_precision'),
'--dependency-prefix', dependency_prefix,
'--allow-system', effective_allow_preinstalled ? 'true' : 'false',
'--vendor-dependency-prefix', wheel_carries_native_bundle ? 'true' : 'false',
]
foreach compiler_launcher : cc_launchers
bundle_command += ['--cc-launcher=' + compiler_launcher]
endforeach
foreach compiler_launcher : cxx_launchers
bundle_command += ['--cxx-launcher=' + compiler_launcher]
endforeach
foreach c_arg : cc_fixed_args
bundle_command += ['--c-arg=' + c_arg]
endforeach
foreach c_arg : get_option('c_args')
bundle_command += ['--c-arg=' + c_arg]
endforeach
foreach c_arg : platform_c_args
bundle_command += ['--c-arg=' + c_arg]
endforeach
foreach cpp_arg : get_option('cpp_args')
bundle_command += ['--cxx-arg=' + cpp_arg]
endforeach
foreach cpp_arg : cxx_fixed_args
bundle_command += ['--cxx-arg=' + cpp_arg]
endforeach
foreach cpp_arg : platform_cpp_args
bundle_command += ['--cxx-arg=' + cpp_arg]
endforeach
foreach c_link_arg : get_option('c_link_args')
bundle_command += ['--c-link-arg=' + c_link_arg]
endforeach
foreach cpp_link_arg : get_option('cpp_link_args')
bundle_command += ['--cxx-link-arg=' + cpp_link_arg]
endforeach
foreach platform_link_arg : platform_link_args
bundle_command += ['--c-link-arg=' + platform_link_arg]
bundle_command += ['--cxx-link-arg=' + platform_link_arg]
endforeach
foreach i : range(mfem_feature_names.length())
bundle_command += [
'--feature',
mfem_feature_names[i] + '=' + (mfem_feature_states[i] ? 'ON' : 'OFF'),
]
endforeach
foreach source_pair : [
['mpich', mpich_source],
['hypre', hypre_source],
['metis', metis_source],
['gslib', gslib_source],
['zlib', zlib_source],
['sundials', sundials_source],
['libceed', libceed_source],
['fms', fms_source],
['algoim', algoim_source],
['blitz', blitz_source],
]
if source_pair[1] != ''
bundle_command += ['--' + source_pair[0] + '-source', source_pair[1]]
endif
endforeach
if bundle_is_python_package and get_option('install_mfem')
mfem_build_target = custom_target(
'mfem-source-bundle',
output: bundle_output_name,
command: bundle_command,
depend_files: bundle_auxiliary_files,
build_by_default: true,
build_always_stale: dependency_prefix != '',
console: true,
install: true,
install_dir: python_build.get_install_dir(),
install_tag: 'python-native',
)
else
mfem_build_target = custom_target(
'mfem-source-bundle',
output: bundle_output_name,
command: bundle_command,
depend_files: bundle_auxiliary_files,
build_by_default: true,
build_always_stale: dependency_prefix != '',
console: true,
)
endif
bundle_compile_args = ['-I' + mfem_runtime_prefix / 'include']
bundle_link_args = [
'-L' + mfem_runtime_prefix / 'lib',
'-L' + mfem_runtime_prefix / 'lib64',
]
if dependency_prefix != '' and not wheel_carries_native_bundle
bundle_compile_args += ['-I' + dependency_prefix / 'include']
bundle_link_args += [
'-L' + dependency_prefix / 'lib',
'-L' + dependency_prefix / 'lib64',
]
endif
bundle_link_args += ['-lmfem']
if mfem_has_mpi
bundle_link_args += ['-lHYPRE', '-lmpi']
endif
if mfem_has_metis
bundle_link_args += ['-lmetis']
endif
if mfem_has_gslib
bundle_link_args += ['-lgs']
endif
if mfem_has_zlib
bundle_link_args += ['-lz']
endif
if mfem_has_sundials
bundle_link_args += [
'-lsundials_nvecserial',
'-lsundials_cvodes',
'-lsundials_arkode',
'-lsundials_kinsol',
'-lsundials_core',
]
if mfem_has_mpi
bundle_link_args += [
'-lsundials_nvecparallel',
'-lsundials_nvecmpiplusx',
]
endif
endif
if mfem_features.get('ceed')
bundle_link_args += ['-lceed']
endif
if mfem_features.get('fms')
bundle_link_args += ['-lfms']
endif
if mfem_features.get('algoim')
bundle_link_args += ['-lblitz']
endif
mfem_dep_parts = []
if mfem_has_openmp or mfem_features.get('legacy_openmp')
mfem_dep_parts += [openmp_dep]
endif
mfem_dep = declare_dependency(
compile_args: bundle_compile_args,
link_args: bundle_link_args,
dependencies: mfem_dep_parts,
sources: mfem_build_target,
version: '4.10',
variables: {
'prefix': mfem_runtime_prefix,
'includedir': mfem_runtime_prefix / 'include',
'libdir': mfem_runtime_prefix / 'lib',
},
)
if get_option('install_mfem') and not bundle_is_python_package
install_bundle = files('../../tools/install_bundle.py')
meson.add_install_script(
python_build,
install_bundle,
'--source', mfem_runtime_prefix,
'--destination', get_option('prefix'),
install_tag: 'runtime',
)
endif
endif
if get_option('build_python') and not get_option('python_install_native') and not system_mfem.found()
error('A source-built Python extension requires -Dpython_install_native=true so its runtime libraries are included in the wheel.')
endif
if get_option('build_python') and get_option('python_install_native') and not get_option('install_mfem')
error('A self-contained Python wheel requires -Dinstall_mfem=true so the native bundle is installed.')
endif
meson.override_dependency('mfem', mfem_dep)

41
build-python/meson.build Normal file
View File

@@ -0,0 +1,41 @@
python_extension = []
python_extension_dir = ''
if get_option('build_python')
if is_wasm
error('The native CPython extension cannot be built for Emscripten. Disable build_python for the WASM profile.')
endif
nanobind_dep = dependency('nanobind', version: '>=2.15.0', fallback: 'nanobind')
extension_rpath_args = []
if host_machine.system() == 'darwin'
extension_rpath_args = [
'-Wl,-rpath,@loader_path/lib',
'-Wl,-rpath,@loader_path/lib64',
]
elif not is_windows
extension_rpath_args = [
'-Wl,-rpath,$ORIGIN/lib',
'-Wl,-rpath,$ORIGIN/lib64',
]
endif
python_extension = python_build.extension_module(
'_core',
'../src/python/bindings.cpp',
dependencies: [nanobind_dep, mfem_dep, template_include_dep],
override_options: ['cpp_std=' + mfem_consumer_cpp_std],
link_args: extension_rpath_args,
build_rpath: '',
install_rpath: '',
install: true,
install_tag: 'python-runtime',
subdir: 'meson_mfem_template',
)
python_extension_dir = meson.current_build_dir()
python_build.install_sources(
'../python/meson_mfem_template/__init__.py',
'../python/meson_mfem_template/_core.pyi',
'../python/meson_mfem_template/py.typed',
subdir: 'meson_mfem_template',
install_tag: 'python-runtime',
)
endif

21
cross/wasm32.ini Normal file
View File

@@ -0,0 +1,21 @@
[binaries]
c = 'emcc'
cpp = 'em++'
ar = 'emar'
strip = 'emstrip'
exe_wrapper = 'node'
[built-in options]
c_args = ['-O1']
cpp_args = ['-O1', '-fwasm-exceptions']
c_link_args = ['-O1', '-sALLOW_MEMORY_GROWTH=1']
cpp_link_args = ['-O1', '-fwasm-exceptions', '-sALLOW_MEMORY_GROWTH=1']
[host_machine]
system = 'emscripten'
cpu_family = 'wasm32'
cpu = 'wasm32'
endian = 'little'
[properties]
needs_exe_wrapper = true

35
examples/meson.build Normal file
View File

@@ -0,0 +1,35 @@
example_deps = [mfem_dep, template_include_dep]
example_build_rpath = mfem_runtime_prefix == '' ? '' : mfem_runtime_prefix / 'lib'
example_install_rpath = ''
if mfem_runtime_prefix != '' and not is_windows and not is_wasm
example_install_rpath = host_machine.system() == 'darwin' ? '@loader_path/../lib' : '$ORIGIN/../lib'
endif
serial_example = []
parallel_example = []
if get_option('build_examples')
serial_example = executable(
'mfem-serial-poisson',
'serial_poisson.cpp',
dependencies: example_deps,
override_options: ['cpp_std=' + mfem_consumer_cpp_std],
build_rpath: example_build_rpath,
install_rpath: example_install_rpath,
install: true,
install_tag: 'examples',
)
if mfem_has_mpi
parallel_example = executable(
'mfem-parallel-hypre',
'parallel_hypre.cpp',
dependencies: example_deps,
override_options: ['cpp_std=' + mfem_consumer_cpp_std],
build_rpath: example_build_rpath,
install_rpath: example_install_rpath,
install: true,
install_tag: 'examples',
)
endif
endif

View File

@@ -0,0 +1,80 @@
#include <meson_mfem_template/config.hpp>
#include <mfem.hpp>
#include <cmath>
#include <iostream>
#if !MESON_MFEM_HAS_MPI || !MESON_MFEM_HAS_HYPRE
#error "This example requires the MPI/Hypre capability"
#endif
int main(int argc, char **argv)
{
mfem::Mpi::Init(argc, argv);
mfem::Hypre::Init();
const int rank = mfem::Mpi::WorldRank();
const char *device_name = argc > 1 ? argv[1] : "cpu";
mfem::Device device(device_name);
mfem::Mesh serial_mesh = mfem::Mesh::MakeCartesian2D(
8, 8, mfem::Element::QUADRILATERAL, true, 1.0, 1.0);
mfem::ParMesh mesh(MPI_COMM_WORLD, serial_mesh);
serial_mesh.Clear();
mfem::H1_FECollection elements(2, mesh.Dimension());
mfem::ParFiniteElementSpace space(&mesh, &elements);
mfem::Array<int> essential_boundary(mesh.bdr_attributes.Max());
essential_boundary = 1;
mfem::Array<int> essential_dofs;
space.GetEssentialTrueDofs(essential_boundary, essential_dofs);
mfem::ConstantCoefficient one(1.0);
mfem::ParLinearForm rhs(&space);
rhs.AddDomainIntegrator(new mfem::DomainLFIntegrator(one));
rhs.Assemble();
mfem::ParGridFunction solution(&space);
solution = 0.0;
mfem::ParBilinearForm diffusion(&space);
diffusion.AddDomainIntegrator(new mfem::DiffusionIntegrator(one));
diffusion.Assemble();
mfem::OperatorPtr matrix;
mfem::Vector linear_rhs;
mfem::Vector linear_solution;
diffusion.FormLinearSystem(
essential_dofs, solution, rhs, matrix, linear_solution, linear_rhs);
mfem::HypreBoomerAMG boomer_amg;
boomer_amg.SetPrintLevel(0);
mfem::CGSolver solver(MPI_COMM_WORLD);
solver.SetPreconditioner(boomer_amg);
solver.SetOperator(*matrix);
solver.SetRelTol(1e-10);
solver.SetAbsTol(0.0);
solver.SetMaxIter(300);
solver.SetPrintLevel(0);
solver.Mult(linear_rhs, linear_solution);
diffusion.RecoverFEMSolution(linear_solution, rhs, solution);
#if MESON_MFEM_HAS_GSLIB
mfem::FindPointsGSLIB find_points(MPI_COMM_WORLD);
#endif
#if MESON_MFEM_HAS_SUNDIALS
mfem::CVODESolver cvode(MPI_COMM_WORLD, CV_BDF);
#endif
const double local_norm = solution.Norml2();
double global_norm = 0.0;
MPI_Allreduce(&local_norm, &global_norm, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
if (rank == 0)
{
std::cout << "MFEM parallel Poisson: ranks=" << mfem::Mpi::WorldSize()
<< " global_dofs=" << space.GlobalTrueVSize()
<< " norm_sum=" << global_norm
<< " device=" << device_name << '\n';
}
return solver.GetConverged() && std::isfinite(global_norm) && global_norm > 0.0
? 0
: 2;
}

View File

@@ -0,0 +1,81 @@
#include <meson_mfem_template/config.hpp>
#include <mfem.hpp>
#include <cmath>
#include <iostream>
int main(int argc, char **argv)
{
const char *device_name = argc > 1 ? argv[1] :
#if MESON_MFEM_HAS_CEED && !defined(__EMSCRIPTEN__)
"ceed-cpu";
#else
"cpu";
#endif
mfem::Device device(device_name);
mfem::Mesh mesh = mfem::Mesh::MakeCartesian2D(
6, 6, mfem::Element::QUADRILATERAL, true, 1.0, 1.0);
const int dimension = mesh.Dimension();
mfem::H1_FECollection elements(2, dimension);
mfem::FiniteElementSpace space(&mesh, &elements);
mfem::Array<int> essential_boundary(mesh.bdr_attributes.Max());
essential_boundary = 1;
mfem::Array<int> essential_dofs;
space.GetEssentialTrueDofs(essential_boundary, essential_dofs);
mfem::ConstantCoefficient one(1.0);
mfem::LinearForm rhs(&space);
rhs.AddDomainIntegrator(new mfem::DomainLFIntegrator(one));
rhs.Assemble();
mfem::GridFunction solution(&space);
solution = 0.0;
mfem::BilinearForm diffusion(&space);
diffusion.AddDomainIntegrator(new mfem::DiffusionIntegrator(one));
if (mfem::Device::Allows(mfem::Backend::CEED_MASK))
{
diffusion.SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
}
diffusion.Assemble();
mfem::OperatorPtr matrix;
mfem::Vector linear_rhs;
mfem::Vector linear_solution;
diffusion.FormLinearSystem(
essential_dofs, solution, rhs, matrix, linear_solution, linear_rhs);
mfem::CGSolver solver;
solver.SetOperator(*matrix);
solver.SetRelTol(1e-12);
solver.SetAbsTol(0.0);
solver.SetMaxIter(200);
solver.SetPrintLevel(0);
solver.Mult(linear_rhs, linear_solution);
diffusion.RecoverFEMSolution(linear_solution, rhs, solution);
#if MESON_MFEM_HAS_FMS
mfem::FMSDataCollection fms_collection("meson-mfem-smoke", &mesh);
fms_collection.SetProtocol("ascii");
#endif
#if MESON_MFEM_HAS_ALGOIM
mfem::FunctionCoefficient level_set(
[](const mfem::Vector &point) { return point[0] + point[1] - 0.2; });
mfem::AlgoimIntegrationRules algoim_rules(2, level_set, 2);
mfem::IntegrationRule cut_rule;
algoim_rules.GetVolumeIntegrationRule(
*mesh.GetElementTransformation(0), cut_rule);
if (cut_rule.GetNPoints() == 0)
{
return 3;
}
#endif
const double norm = solution.Norml2();
std::cout << "MFEM serial Poisson: dofs=" << space.GetTrueVSize()
<< " l2=" << norm << " device=" << device_name << '\n';
return solver.GetConverged() && std::isfinite(norm) && norm > 0.0 ? 0 : 2;
}

View File

@@ -0,0 +1,261 @@
#pragma once
#define MESON_MFEM_TEMPLATE_VERSION @MESON_MFEM_TEMPLATE_VERSION@
#mesondefine01 MESON_MFEM_USING_BUNDLED_MFEM
#include <mfem/config/config.hpp>
#ifdef MFEM_USE_MPI
#define MESON_MFEM_HAS_MPI 1
#else
#define MESON_MFEM_HAS_MPI 0
#endif
#define MESON_MFEM_HAS_HYPRE MESON_MFEM_HAS_MPI
#ifdef MFEM_USE_METIS
#define MESON_MFEM_HAS_METIS 1
#else
#define MESON_MFEM_HAS_METIS 0
#endif
#ifdef MFEM_USE_EXCEPTIONS
#define MESON_MFEM_HAS_EXCEPTIONS 1
#else
#define MESON_MFEM_HAS_EXCEPTIONS 0
#endif
#ifdef MFEM_USE_ZLIB
#define MESON_MFEM_HAS_ZLIB 1
#else
#define MESON_MFEM_HAS_ZLIB 0
#endif
#ifdef MFEM_USE_LIBUNWIND
#define MESON_MFEM_HAS_LIBUNWIND 1
#else
#define MESON_MFEM_HAS_LIBUNWIND 0
#endif
#ifdef MFEM_USE_LAPACK
#define MESON_MFEM_HAS_LAPACK 1
#else
#define MESON_MFEM_HAS_LAPACK 0
#endif
#ifdef MFEM_THREAD_SAFE
#define MESON_MFEM_HAS_THREAD_SAFE 1
#else
#define MESON_MFEM_HAS_THREAD_SAFE 0
#endif
#ifdef MFEM_USE_OPENMP
#define MESON_MFEM_HAS_OPENMP 1
#else
#define MESON_MFEM_HAS_OPENMP 0
#endif
#ifdef MFEM_USE_LEGACY_OPENMP
#define MESON_MFEM_HAS_LEGACY_OPENMP 1
#else
#define MESON_MFEM_HAS_LEGACY_OPENMP 0
#endif
#ifdef MFEM_USE_MEMALLOC
#define MESON_MFEM_HAS_MEMALLOC 1
#else
#define MESON_MFEM_HAS_MEMALLOC 0
#endif
#ifdef MFEM_USE_SUNDIALS
#define MESON_MFEM_HAS_SUNDIALS 1
#else
#define MESON_MFEM_HAS_SUNDIALS 0
#endif
#ifdef MFEM_USE_SUITESPARSE
#define MESON_MFEM_HAS_SUITESPARSE 1
#else
#define MESON_MFEM_HAS_SUITESPARSE 0
#endif
#ifdef MFEM_USE_SUPERLU
#define MESON_MFEM_HAS_SUPERLU 1
#else
#define MESON_MFEM_HAS_SUPERLU 0
#endif
#ifdef MFEM_USE_SUPERLU5
#define MESON_MFEM_HAS_SUPERLU5 1
#else
#define MESON_MFEM_HAS_SUPERLU5 0
#endif
#ifdef MFEM_USE_MUMPS
#define MESON_MFEM_HAS_MUMPS 1
#else
#define MESON_MFEM_HAS_MUMPS 0
#endif
#ifdef MFEM_USE_STRUMPACK
#define MESON_MFEM_HAS_STRUMPACK 1
#else
#define MESON_MFEM_HAS_STRUMPACK 0
#endif
#ifdef MFEM_USE_CUDSS
#define MESON_MFEM_HAS_CUDSS 1
#else
#define MESON_MFEM_HAS_CUDSS 0
#endif
#ifdef MFEM_USE_GINKGO
#define MESON_MFEM_HAS_GINKGO 1
#else
#define MESON_MFEM_HAS_GINKGO 0
#endif
#ifdef MFEM_USE_AMGX
#define MESON_MFEM_HAS_AMGX 1
#else
#define MESON_MFEM_HAS_AMGX 0
#endif
#ifdef MFEM_USE_MAGMA
#define MESON_MFEM_HAS_MAGMA 1
#else
#define MESON_MFEM_HAS_MAGMA 0
#endif
#ifdef MFEM_USE_GNUTLS
#define MESON_MFEM_HAS_GNUTLS 1
#else
#define MESON_MFEM_HAS_GNUTLS 0
#endif
#ifdef MFEM_USE_GSLIB
#define MESON_MFEM_HAS_GSLIB 1
#else
#define MESON_MFEM_HAS_GSLIB 0
#endif
#ifdef MFEM_USE_HDF5
#define MESON_MFEM_HAS_HDF5 1
#else
#define MESON_MFEM_HAS_HDF5 0
#endif
#ifdef MFEM_USE_NETCDF
#define MESON_MFEM_HAS_NETCDF 1
#else
#define MESON_MFEM_HAS_NETCDF 0
#endif
#ifdef MFEM_USE_PETSC
#define MESON_MFEM_HAS_PETSC 1
#else
#define MESON_MFEM_HAS_PETSC 0
#endif
#ifdef MFEM_USE_SLEPC
#define MESON_MFEM_HAS_SLEPC 1
#else
#define MESON_MFEM_HAS_SLEPC 0
#endif
#ifdef MFEM_USE_MPFR
#define MESON_MFEM_HAS_MPFR 1
#else
#define MESON_MFEM_HAS_MPFR 0
#endif
#ifdef MFEM_USE_SIDRE
#define MESON_MFEM_HAS_SIDRE 1
#else
#define MESON_MFEM_HAS_SIDRE 0
#endif
#ifdef MFEM_USE_FMS
#define MESON_MFEM_HAS_FMS 1
#else
#define MESON_MFEM_HAS_FMS 0
#endif
#ifdef MFEM_USE_CONDUIT
#define MESON_MFEM_HAS_CONDUIT 1
#else
#define MESON_MFEM_HAS_CONDUIT 0
#endif
#ifdef MFEM_USE_PUMI
#define MESON_MFEM_HAS_PUMI 1
#else
#define MESON_MFEM_HAS_PUMI 0
#endif
#ifdef MFEM_USE_HIOP
#define MESON_MFEM_HAS_HIOP 1
#else
#define MESON_MFEM_HAS_HIOP 0
#endif
#ifdef MFEM_USE_CUDA
#define MESON_MFEM_HAS_CUDA 1
#else
#define MESON_MFEM_HAS_CUDA 0
#endif
#ifdef MFEM_USE_HIP
#define MESON_MFEM_HAS_HIP 1
#else
#define MESON_MFEM_HAS_HIP 0
#endif
#ifdef MFEM_USE_OCCA
#define MESON_MFEM_HAS_OCCA 1
#else
#define MESON_MFEM_HAS_OCCA 0
#endif
#ifdef MFEM_USE_RAJA
#define MESON_MFEM_HAS_RAJA 1
#else
#define MESON_MFEM_HAS_RAJA 0
#endif
#ifdef MFEM_USE_CEED
#define MESON_MFEM_HAS_CEED 1
#else
#define MESON_MFEM_HAS_CEED 0
#endif
#ifdef MFEM_USE_UMPIRE
#define MESON_MFEM_HAS_UMPIRE 1
#else
#define MESON_MFEM_HAS_UMPIRE 0
#endif
#ifdef MFEM_USE_SIMD
#define MESON_MFEM_HAS_SIMD 1
#else
#define MESON_MFEM_HAS_SIMD 0
#endif
#ifdef MFEM_USE_ADIOS2
#define MESON_MFEM_HAS_ADIOS2 1
#else
#define MESON_MFEM_HAS_ADIOS2 0
#endif
#ifdef MFEM_USE_CALIPER
#define MESON_MFEM_HAS_CALIPER 1
#else
#define MESON_MFEM_HAS_CALIPER 0
#endif
#ifdef MFEM_USE_ALGOIM
#define MESON_MFEM_HAS_ALGOIM 1
#else
#define MESON_MFEM_HAS_ALGOIM 0
#endif
#ifdef MFEM_USE_MKL_CPARDISO
#define MESON_MFEM_HAS_MKL_CPARDISO 1
#else
#define MESON_MFEM_HAS_MKL_CPARDISO 0
#endif
#ifdef MFEM_USE_MKL_PARDISO
#define MESON_MFEM_HAS_MKL_PARDISO 1
#else
#define MESON_MFEM_HAS_MKL_PARDISO 0
#endif
#ifdef MFEM_USE_ADFORWARD
#define MESON_MFEM_HAS_ADFORWARD 1
#else
#define MESON_MFEM_HAS_ADFORWARD 0
#endif
#ifdef MFEM_USE_CODIPACK
#define MESON_MFEM_HAS_CODIPACK 1
#else
#define MESON_MFEM_HAS_CODIPACK 0
#endif
#ifdef MFEM_USE_BENCHMARK
#define MESON_MFEM_HAS_BENCHMARK 1
#else
#define MESON_MFEM_HAS_BENCHMARK 0
#endif
#define MESON_MFEM_HAS_PARELAG 0
#define MESON_MFEM_HAS_TRIBOL 0
#ifdef MFEM_USE_ENZYME
#define MESON_MFEM_HAS_ENZYME 1
#else
#define MESON_MFEM_HAS_ENZYME 0
#endif
#ifdef MFEM_USE_MOONOLITH
#define MESON_MFEM_HAS_MOONOLITH 1
#else
#define MESON_MFEM_HAS_MOONOLITH 0
#endif
#ifdef MFEM_USE_SIMMETRIX
#define MESON_MFEM_HAS_SIMMETRIX 1
#else
#define MESON_MFEM_HAS_SIMMETRIX 0
#endif

View File

@@ -0,0 +1,19 @@
mfem_config = configuration_data()
mfem_config.set_quoted('MESON_MFEM_TEMPLATE_VERSION', meson.project_version())
mfem_config.set10('MESON_MFEM_USING_BUNDLED_MFEM', mfem_provider != 'system')
mfem_config_header = configure_file(
input: 'config.hpp.in',
output: 'config.hpp',
configuration: mfem_config,
install: true,
install_tag: get_option('build_python') ? 'python-runtime' : 'devel',
install_dir: get_option('build_python')
? python_build.get_install_dir() / 'meson_mfem_template' / 'include' / 'meson_mfem_template'
: get_option('includedir') / 'meson_mfem_template',
)
template_include_dep = declare_dependency(
include_directories: include_directories('..'),
sources: mfem_config_header,
)

58
meson.build Normal file
View File

@@ -0,0 +1,58 @@
project(
'meson-mfem-template',
['c', 'cpp'],
version: '0.1.0',
meson_version: '>=1.9.1',
default_options: [
'c_std=c11',
'cpp_std=c++23',
'warning_level=2',
],
)
python_mod = import('python')
fs = import('fs')
cc = meson.get_compiler('c')
cpp = meson.get_compiler('cpp')
platform_c_args = []
platform_cpp_args = []
platform_link_args = []
if host_machine.system() == 'darwin' and get_option('macos_deployment_target') != ''
macos_minimum_flag = '-mmacosx-version-min=' + get_option('macos_deployment_target')
platform_c_args += [macos_minimum_flag]
platform_cpp_args += [macos_minimum_flag]
platform_link_args += [macos_minimum_flag]
add_project_arguments(macos_minimum_flag, language: ['c', 'cpp'])
add_project_link_arguments(macos_minimum_flag, language: ['c', 'cpp'])
endif
subdir('build-check')
subdir('build-config')
subdir('include/meson_mfem_template')
subdir('examples')
subdir('build-python')
subdir('tests')
summary(
{
'MFEM provider': mfem_provider,
'dependency profile': get_option('feature_profile'),
'allow preinstalled': get_option('allow_preinstalled'),
'parallel MPI/Hypre': mfem_has_mpi,
'METIS': mfem_has_metis,
'CUDA': mfem_has_cuda,
'HIP': mfem_has_hip,
'OpenMP': mfem_has_openmp,
'GSLIB': mfem_has_gslib,
'zlib': mfem_has_zlib,
'SUNDIALS': mfem_has_sundials,
'libCEED': mfem_features.get('ceed'),
'FMS': mfem_features.get('fms'),
'Algoim': mfem_features.get('algoim'),
'Python bindings': get_option('build_python'),
'examples': get_option('build_examples'),
'tests': get_option('build_tests'),
},
section: 'meson-mfem-template ' + meson.project_version(),
bool_yn: true,
)

66
meson_options.txt Normal file
View File

@@ -0,0 +1,66 @@
option('allow_preinstalled', type: 'boolean', value: true, description: 'Allow one complete, feature-compatible system MFEM. Set false for the dependency-hermetic source fallback.')
option('feature_profile', type: 'combo', choices: ['minimal', 'portable', 'full'], value: 'portable', description: 'Defaults for auto features. full is strict and requires all selected non-bundled TPLs in dependency_prefix.')
option('build_python', type: 'boolean', value: false, description: 'Build the nanobind Python extension and install native assets inside the Python package.')
option('build_examples', type: 'boolean', value: true, description: 'Build MFEM capability examples.')
option('build_tests', type: 'boolean', value: true, description: 'Register serial, parallel, and Python smoke tests when applicable.')
option('install_mfem', type: 'boolean', value: true, description: 'Install the source-built MFEM bundle and its runtime dependencies.')
option('dependency_prefix', type: 'string', value: '', description: 'Explicit prefix containing optional TPLs. Used even when allow_preinstalled=false.')
option('jobs', type: 'integer', min: 1, max: 1024, value: 4, description: 'Parallel jobs for the CMake/Make source bundle.')
option('mfem_cuda_arch', type: 'string', value: 'native', description: 'CUDA architectures understood by CMake, for example 80;86. native requires a recent CMake/toolkit.')
option('mfem_hip_arch', type: 'string', value: '', description: 'HIP GPU target, for example gfx90a. Empty lets the toolchain choose.')
option('mfem_precision', type: 'combo', choices: ['double', 'single'], value: 'double', description: 'Floating-point precision used consistently by MFEM and Hypre.')
option('python_install_native', type: 'boolean', value: true, description: 'Install MFEM headers, CMake/pkg-config data, and runtime libraries under the Python package.')
option('macos_deployment_target', type: 'string', value: '11.0', description: 'Minimum macOS version for native artifacts; ignored on other systems. Empty uses the toolchain default.')
option('mfem_mpi', type: 'feature', value: 'auto', description: 'MPI parallel MFEM; implies Hypre.')
option('mfem_metis', type: 'feature', value: 'auto', description: 'METIS graph partitioning.')
option('mfem_exceptions', type: 'feature', value: 'auto', description: 'MFEM C++ exceptions.')
option('mfem_zlib', type: 'feature', value: 'auto', description: 'zlib compressed streams.')
option('mfem_libunwind', type: 'feature', value: 'auto', description: 'libunwind backtraces.')
option('mfem_lapack', type: 'feature', value: 'auto', description: 'BLAS/LAPACK solvers.')
option('mfem_thread_safe', type: 'feature', value: 'auto', description: 'MFEM thread-safe mode.')
option('mfem_openmp', type: 'feature', value: 'auto', description: 'OpenMP backend.')
option('mfem_legacy_openmp', type: 'feature', value: 'disabled', description: 'Legacy OpenMP backend (mutually exclusive with modern device backends).')
option('mfem_memalloc', type: 'feature', value: 'enabled', description: 'MFEM internal memory allocator.')
option('mfem_sundials', type: 'feature', value: 'auto', description: 'SUNDIALS ODE/nonlinear solvers.')
option('mfem_suitesparse', type: 'feature', value: 'auto', description: 'SuiteSparse direct solvers.')
option('mfem_superlu', type: 'feature', value: 'auto', description: 'SuperLU_DIST.')
option('mfem_superlu5', type: 'feature', value: 'disabled', description: 'Compatibility with old SuperLU_DIST 5.1.')
option('mfem_mumps', type: 'feature', value: 'auto', description: 'MUMPS parallel direct solver.')
option('mfem_strumpack', type: 'feature', value: 'auto', description: 'STRUMPACK parallel solver.')
option('mfem_cudss', type: 'feature', value: 'auto', description: 'NVIDIA cuDSS (CUDA only, vendor binary dependency).')
option('mfem_ginkgo', type: 'feature', value: 'auto', description: 'Ginkgo solvers.')
option('mfem_amgx', type: 'feature', value: 'auto', description: 'NVIDIA AmgX.')
option('mfem_magma', type: 'feature', value: 'auto', description: 'MAGMA dense linear algebra.')
option('mfem_gnutls', type: 'feature', value: 'auto', description: 'GnuTLS socket security.')
option('mfem_gslib', type: 'feature', value: 'auto', description: 'GSLIB FindPoints support.')
option('mfem_hdf5', type: 'feature', value: 'auto', description: 'HDF5 data support.')
option('mfem_netcdf', type: 'feature', value: 'auto', description: 'NetCDF data support.')
option('mfem_petsc', type: 'feature', value: 'auto', description: 'PETSc solvers.')
option('mfem_slepc', type: 'feature', value: 'auto', description: 'SLEPc eigensolvers; implies PETSc.')
option('mfem_mpfr', type: 'feature', value: 'auto', description: 'MPFR arbitrary precision support.')
option('mfem_sidre', type: 'feature', value: 'auto', description: 'Axom/Sidre support.')
option('mfem_fms', type: 'feature', value: 'auto', description: 'FMS data model.')
option('mfem_conduit', type: 'feature', value: 'auto', description: 'Conduit data support.')
option('mfem_pumi', type: 'feature', value: 'auto', description: 'PUMI meshes.')
option('mfem_hiop', type: 'feature', value: 'auto', description: 'HiOp optimization.')
option('mfem_cuda', type: 'feature', value: 'auto', description: 'NVIDIA CUDA backend; requires a host CUDA toolkit.')
option('mfem_hip', type: 'feature', value: 'auto', description: 'AMD HIP backend; requires a host ROCm toolkit.')
option('mfem_occa', type: 'feature', value: 'auto', description: 'OCCA device backend.')
option('mfem_raja', type: 'feature', value: 'auto', description: 'RAJA device backend; raises MFEM to C++20.')
option('mfem_ceed', type: 'feature', value: 'auto', description: 'libCEED operator backend.')
option('mfem_umpire', type: 'feature', value: 'auto', description: 'Umpire memory manager; raises MFEM to C++20.')
option('mfem_simd', type: 'feature', value: 'auto', description: 'SIMD intrinsics.')
option('mfem_adios2', type: 'feature', value: 'auto', description: 'ADIOS2 I/O.')
option('mfem_caliper', type: 'feature', value: 'auto', description: 'Caliper profiling.')
option('mfem_algoim', type: 'feature', value: 'auto', description: 'Algoim implicit geometry support.')
option('mfem_mkl_cpardiso', type: 'feature', value: 'auto', description: 'Intel MKL Cluster PARDISO.')
option('mfem_mkl_pardiso', type: 'feature', value: 'auto', description: 'Intel MKL PARDISO.')
option('mfem_adforward', type: 'feature', value: 'auto', description: 'Forward mode for supported automatic differentiation packages.')
option('mfem_codipack', type: 'feature', value: 'auto', description: 'CoDiPack automatic differentiation.')
option('mfem_benchmark', type: 'feature', value: 'disabled', description: 'Upstream benchmark-program build switch; enabling is rejected because it is not an installed MFEM library capability.')
option('mfem_parelag', type: 'feature', value: 'disabled', description: 'Upstream ParELAG miniapp-only switch; enabling is rejected because it is not an installed MFEM library capability.')
option('mfem_tribol', type: 'feature', value: 'disabled', description: 'Upstream Tribol miniapp-only switch; enabling is rejected because it is not an installed MFEM library capability.')
option('mfem_enzyme', type: 'feature', value: 'auto', description: 'Enzyme AD plugin; requires matching Clang/LLVM.')
option('mfem_moonolith', type: 'feature', value: 'auto', description: 'Moonolith transfer miniapp integration.')
option('mfem_simmetrix', type: 'feature', value: 'auto', description: 'Simmetrix commercial mesh integration.')

41
pyproject.toml Normal file
View File

@@ -0,0 +1,41 @@
[build-system]
build-backend = "mesonpy"
requires = [
"meson-python>=0.20",
"meson>=1.9.1",
"ninja>=1.11",
"cmake>=3.24",
"patchelf>=0.17; sys_platform == 'linux'",
]
[project]
name = "meson-mfem-template"
dynamic = ["version"]
description = "Portable Meson source bundle and nanobind validation module for MFEM"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
license-files = ["LICENSE"]
[tool.meson-python]
allow-windows-internal-shared-libs = true
[tool.meson-python.args]
setup = [
"-Dbuildtype=release",
"-Dallow_preinstalled=false",
"-Dfeature_profile=portable",
"-Dbuild_python=true",
"-Dbuild_examples=false",
"-Dbuild_tests=false",
"-Dinstall_mfem=true",
"-Dpython_install_native=true",
"-Dmfem_openmp=disabled",
"-Dmfem_cuda=disabled",
"-Dmfem_hip=disabled",
"--force-fallback-for=nanobind,robin-map",
]
install = [
"--tags=python-runtime,python-native",
"--skip-subprojects",
]

View File

@@ -0,0 +1,46 @@
from __future__ import annotations
import os
from pathlib import Path
def _native_search_paths() -> list[Path]:
package = Path(__file__).resolve().parent
return [package / "lib", package / "lib64", package / "bin"]
_dll_directory_handles = []
if os.name == "nt" and hasattr(os, "add_dll_directory"):
for _directory in _native_search_paths():
if _directory.is_dir():
_dll_directory_handles.append(os.add_dll_directory(str(_directory)))
from ._core import capabilities, mfem_version, serial_poisson_dofs
__all__ = [
"capabilities",
"get_cmake_prefix",
"get_include",
"get_lib",
"mfem_version",
"serial_poisson_dofs",
]
def _package_dir() -> Path:
return Path(__file__).resolve().parent
def get_include() -> str:
"""Return the wheel-local include directory containing MFEM headers."""
return str(_package_dir() / "include")
def get_lib() -> str:
"""Return the wheel-local directory containing MFEM runtime libraries."""
return str(_package_dir() / "lib")
def get_cmake_prefix() -> str:
"""Return the prefix that can be appended to CMAKE_PREFIX_PATH."""
return str(_package_dir())

View File

@@ -0,0 +1,4 @@
def mfem_version() -> str: ...
def capabilities() -> dict[str, bool]: ...
def serial_poisson_dofs(cells: int = 4, order: int = 2) -> int: ...

View File

@@ -0,0 +1 @@

91
src/python/bindings.cpp Normal file
View File

@@ -0,0 +1,91 @@
#include <meson_mfem_template/config.hpp>
#include <mfem.hpp>
#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>
#include <cmath>
#include <stdexcept>
#include <string>
namespace nb = nanobind;
namespace
{
int serial_poisson_dofs(int cells, int order)
{
if (cells < 2 || order < 1)
{
throw nb::value_error("cells must be >= 2 and order must be >= 1");
}
mfem::Mesh mesh = mfem::Mesh::MakeCartesian2D(
cells, cells, mfem::Element::QUADRILATERAL, true, 1.0, 1.0);
mfem::H1_FECollection elements(order, mesh.Dimension());
mfem::FiniteElementSpace space(&mesh, &elements);
mfem::Array<int> essential_boundary(mesh.bdr_attributes.Max());
essential_boundary = 1;
mfem::Array<int> essential_dofs;
space.GetEssentialTrueDofs(essential_boundary, essential_dofs);
mfem::ConstantCoefficient one(1.0);
mfem::LinearForm rhs(&space);
rhs.AddDomainIntegrator(new mfem::DomainLFIntegrator(one));
rhs.Assemble();
mfem::GridFunction solution(&space);
solution = 0.0;
mfem::BilinearForm diffusion(&space);
diffusion.AddDomainIntegrator(new mfem::DiffusionIntegrator(one));
diffusion.Assemble();
mfem::OperatorPtr matrix;
mfem::Vector linear_rhs;
mfem::Vector linear_solution;
diffusion.FormLinearSystem(
essential_dofs, solution, rhs, matrix, linear_solution, linear_rhs);
mfem::GSSmoother smoother(static_cast<mfem::SparseMatrix &>(*matrix));
mfem::CGSolver solver;
solver.SetOperator(*matrix);
solver.SetPreconditioner(smoother);
solver.SetRelTol(1e-10);
solver.SetMaxIter(200);
solver.SetPrintLevel(0);
solver.Mult(linear_rhs, linear_solution);
if (!solver.GetConverged())
{
throw std::runtime_error("MFEM CG solve did not converge");
}
return space.GetTrueVSize();
}
nb::dict capabilities()
{
nb::dict result;
result["mpi"] = MESON_MFEM_HAS_MPI != 0;
result["hypre"] = MESON_MFEM_HAS_HYPRE != 0;
result["metis"] = MESON_MFEM_HAS_METIS != 0;
result["cuda"] = MESON_MFEM_HAS_CUDA != 0;
result["hip"] = MESON_MFEM_HAS_HIP != 0;
result["openmp"] = MESON_MFEM_HAS_OPENMP != 0;
result["gslib"] = MESON_MFEM_HAS_GSLIB != 0;
result["zlib"] = MESON_MFEM_HAS_ZLIB != 0;
result["sundials"] = MESON_MFEM_HAS_SUNDIALS != 0;
result["ceed"] = MESON_MFEM_HAS_CEED != 0;
result["fms"] = MESON_MFEM_HAS_FMS != 0;
result["algoim"] = MESON_MFEM_HAS_ALGOIM != 0;
result["simd"] = MESON_MFEM_HAS_SIMD != 0;
result["bundled_mfem"] = MESON_MFEM_USING_BUNDLED_MFEM != 0;
return result;
}
} // namespace
NB_MODULE(_core, module)
{
module.doc() = "Small nanobind validation module for the Meson MFEM bundle";
module.def("mfem_version", []() { return std::string(MFEM_VERSION_STRING); });
module.def("capabilities", &capabilities);
module.def(
"serial_poisson_dofs",
&serial_poisson_dofs,
nb::arg("cells") = 4,
nb::arg("order") = 2,
"Assemble and solve a small MFEM Poisson problem; return true DOFs.");
}

0
subprojects/.wraplock Normal file
View File

10
subprojects/algoim.wrap Normal file
View File

@@ -0,0 +1,10 @@
[wrap-file]
directory = algoim-9c9ca0ef094d8ab0390ed36367a1151b459bbe0a
source_url = https://github.com/algoim/algoim/archive/9c9ca0ef094d8ab0390ed36367a1151b459bbe0a.tar.gz
source_filename = algoim-9c9ca0ef094d8ab0390ed36367a1151b459bbe0a.tar.gz
source_hash = 8b0cbeb3821665c57bbd88426cee7ffca21c95cb2e37edf2f670139b1e50c2cf
patch_directory = algoim
[provide]
algoim = algoim_dep

10
subprojects/blitz.wrap Normal file
View File

@@ -0,0 +1,10 @@
[wrap-file]
directory = blitz-1.0.2
source_url = https://github.com/blitzpp/blitz/archive/refs/tags/1.0.2.tar.gz
source_filename = blitz-1.0.2.tar.gz
source_hash = 500db9c3b2617e1f03d0e548977aec10d36811ba1c43bb5ef250c0e3853ae1c2
patch_directory = blitz
[provide]
blitz = blitz_dep

10
subprojects/fms.wrap Normal file
View File

@@ -0,0 +1,10 @@
[wrap-file]
directory = FMS-0.2
source_url = https://github.com/CEED/FMS/archive/refs/tags/v0.2.tar.gz
source_filename = fms-0.2.tar.gz
source_hash = 872489a1325b247968dbb7265b8736660af94121a86c93f7938441ce7478183e
patch_directory = fms
[provide]
fms = fms_dep

7
subprojects/gslib.wrap Normal file
View File

@@ -0,0 +1,7 @@
[wrap-file]
directory = gslib-1.0.9
source_url = https://github.com/Nek5000/gslib/archive/refs/tags/v1.0.9.tar.gz
source_filename = gslib-1.0.9.tar.gz
source_hash = 572c78b1ca4ddda75ec85c6299ee0597459f4dbf5170b9faa6de4c435dcdc80b
patch_directory = gslib

7
subprojects/hypre.wrap Normal file
View File

@@ -0,0 +1,7 @@
[wrap-file]
directory = hypre-2.33.0
source_url = https://github.com/hypre-space/hypre/archive/refs/tags/v2.33.0.tar.gz
source_filename = hypre-2.33.0.tar.gz
source_hash = 0f9103c34bce7a5dcbdb79a502720fc8aab4db9fd0146e0791cde7ec878f27da
patch_directory = hypre

10
subprojects/libceed.wrap Normal file
View File

@@ -0,0 +1,10 @@
[wrap-file]
directory = libCEED-0.12.0
source_url = https://github.com/CEED/libCEED/archive/refs/tags/v0.12.0.tar.gz
source_filename = libceed-0.12.0.tar.gz
source_hash = 2d94c218f6ab1bed072d11b70b0a0e70a5720fdf3d61db924e89909424a2f93e
patch_directory = libceed
[provide]
libceed = libceed_dep

7
subprojects/metis.wrap Normal file
View File

@@ -0,0 +1,7 @@
[wrap-file]
directory = tpls-b60352fbe9675d374b00828055e55be4584c7995
source_url = https://github.com/mfem/tpls/archive/b60352fbe9675d374b00828055e55be4584c7995.tar.gz
source_filename = mfem-tpls-b60352f.tar.gz
source_hash = 026192230134b8e8028ef4969553f180c7f64fca8a156993d2db38868536f71f
patch_directory = metis

7
subprojects/mfem.wrap Normal file
View File

@@ -0,0 +1,7 @@
[wrap-file]
directory = mfem-4.10
source_url = https://github.com/mfem/mfem/archive/refs/tags/v4.10.tar.gz
source_filename = mfem-4.10.tar.gz
source_hash = d5aabe991b8b5569aa26e2b5d4b59ac617ee10ebac3d45fd6ff9c74b2c1a47dd
patch_directory = mfem

7
subprojects/mpich.wrap Normal file
View File

@@ -0,0 +1,7 @@
[wrap-file]
directory = mpich-4.3.2
source_url = https://www.mpich.org/static/downloads/4.3.2/mpich-4.3.2.tar.gz
source_filename = mpich-4.3.2.tar.gz
source_hash = 47d774587a7156a53752218c811c852e70ac44db9c502dc3f399b4cb817e3818
patch_directory = mpich

15
subprojects/nanobind.wrap Normal file
View File

@@ -0,0 +1,15 @@
[wrap-file]
directory = nanobind-2.15.0
source_url = https://github.com/wjakob/nanobind/archive/refs/tags/v2.15.0.tar.gz
source_filename = nanobind-2.15.0.tar.gz
source_hash = 36c8760b3acb25643cd89d549782d8c67bd82ce54c6238608787c22a34fd490f
source_fallback_url = https://wrapdb.mesonbuild.com/v2/nanobind_2.15.0-1/get_source/nanobind-2.15.0.tar.gz
patch_filename = nanobind_2.15.0-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/nanobind_2.15.0-1/get_patch
patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/nanobind_2.15.0-1/nanobind_2.15.0-1_patch.zip
patch_hash = 4a3956c67792830e0c85dff3cf88b30a5547bd087c33842fa5f1596ca86ccabf
wrapdb_version = 2.15.0-1
[provide]
dependency_names = nanobind

View File

@@ -0,0 +1,4 @@
project('algoim-source')
algoim_source_dir = meson.current_source_dir()
algoim_dep = declare_dependency(variables: {'source_dir': algoim_source_dir})

View File

@@ -0,0 +1,3 @@
project('blitz-source', version: '1.0.2')
blitz_source_dir = meson.current_source_dir()
blitz_dep = declare_dependency(variables: {'source_dir': blitz_source_dir})

View File

@@ -0,0 +1,3 @@
project('fms-source', version: '0.2')
fms_source_dir = meson.current_source_dir()
fms_dep = declare_dependency(variables: {'source_dir': fms_source_dir})

View File

@@ -0,0 +1,3 @@
project('gslib-source', 'c', version: '1.0.9', meson_version: '>=1.5.0')
gslib_source_dir = meson.current_source_dir()

View File

@@ -0,0 +1,3 @@
project('hypre-source', 'c', version: '2.33.0', meson_version: '>=1.5.0')
hypre_source_dir = meson.current_source_dir()

View File

@@ -0,0 +1,4 @@
project('libceed-source', version: '0.12.0')
libceed_source_dir = meson.current_source_dir()
libceed_dep = declare_dependency(variables: {'source_dir': libceed_source_dir})

View File

@@ -0,0 +1,2 @@
project('metis-source', 'c', version: '5.1.0', meson_version: '>=1.5.0')
metis_source_dir = meson.current_source_dir()

View File

@@ -0,0 +1,3 @@
project('mfem-source', 'cpp', version: '4.10', meson_version: '>=1.5.0')
mfem_source_dir = meson.current_source_dir()

View File

@@ -0,0 +1,3 @@
project('mpich-source', 'c', version: '4.3.2', meson_version: '>=1.5.0')
mpich_source_dir = meson.current_source_dir()

View File

@@ -0,0 +1,3 @@
project('sundials-source', ['c', 'cpp'], version: '7.8.0', meson_version: '>=1.5.0')
sundials_source_dir = meson.current_source_dir()

View File

@@ -0,0 +1,3 @@
project('zlib-source', 'c', version: '1.3.1', meson_version: '>=1.5.0')
zlib_source_dir = meson.current_source_dir()

View File

@@ -0,0 +1,15 @@
[wrap-file]
directory = robin-map-1.4.1
source_url = https://github.com/Tessil/robin-map/archive/refs/tags/v1.4.1.tar.gz
source_filename = robin-map-1.4.1.tar.gz
source_hash = 0e3f53a377fdcdc5f9fed7a4c0d4f99e82bbb64175233bd13427fef9a771f4a1
source_fallback_url = https://wrapdb.mesonbuild.com/v2/robin-map_1.4.1-1/get_source/robin-map-1.4.1.tar.gz
patch_filename = robin-map_1.4.1-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/robin-map_1.4.1-1/get_patch
patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/robin-map_1.4.1-1/robin-map_1.4.1-1_patch.zip
patch_hash = 8137d5283004db445d744d603e600119047fed865cbd740f8fec4421dc1a45b6
wrapdb_version = 1.4.1-1
[provide]
dependency_names = tsl-robin-map

View File

@@ -0,0 +1,7 @@
[wrap-file]
directory = sundials-7.8.0
source_url = https://github.com/LLNL/sundials/archive/refs/tags/v7.8.0.tar.gz
source_filename = sundials-7.8.0.tar.gz
source_hash = c2ca15a16d7ae0d79cf1c2c288335f16b0f1c2c6d349db20aff2ce3fd1296d5a
patch_directory = sundials

7
subprojects/zlib.wrap Normal file
View File

@@ -0,0 +1,7 @@
[wrap-file]
directory = zlib-1.3.1
source_url = https://github.com/madler/zlib/archive/refs/tags/v1.3.1.tar.gz
source_filename = zlib-1.3.1.tar.gz
source_hash = 17e88863f3600672ab49182f217281b6fc4d3c762bde361935e436a95214d05c
patch_directory = zlib

86
tests/meson.build Normal file
View File

@@ -0,0 +1,86 @@
if get_option('build_tests') and get_option('build_examples')
runtime_environment = environment()
if (
mfem_runtime_prefix != '' and dependency_prefix != '' and
not wheel_carries_native_bundle
)
runtime_environment.prepend('PATH', dependency_prefix / 'bin')
if host_machine.system() == 'darwin'
runtime_environment.prepend('DYLD_LIBRARY_PATH', dependency_prefix / 'lib64')
runtime_environment.prepend('DYLD_LIBRARY_PATH', dependency_prefix / 'lib')
elif host_machine.system() != 'windows' and not is_wasm
runtime_environment.prepend('LD_LIBRARY_PATH', dependency_prefix / 'lib64')
runtime_environment.prepend('LD_LIBRARY_PATH', dependency_prefix / 'lib')
endif
endif
if mfem_runtime_prefix != ''
runtime_environment.prepend('PATH', mfem_runtime_prefix / 'bin')
if host_machine.system() == 'darwin'
runtime_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
runtime_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
elif host_machine.system() != 'windows' and not is_wasm
runtime_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
runtime_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
endif
endif
test(
'serial-poisson',
serial_example,
env: runtime_environment,
timeout: 120,
)
if mfem_has_mpi
if mpi_launcher_from_dependency and not wheel_carries_native_bundle
mpi_launcher_program = find_program(
dependency_prefix / 'bin' / 'mpiexec',
dependency_prefix / 'bin' / 'mpirun',
required: true,
)
mpi_launcher = mpi_launcher_program.full_path()
elif mfem_runtime_prefix != ''
mpi_launcher = mfem_runtime_prefix / 'bin' / 'mpiexec'
else
mpi_launcher_program = find_program('mpiexec', 'mpirun', required: true)
mpi_launcher = mpi_launcher_program.full_path()
endif
test(
'parallel-hypre-boomeramg',
python_build,
args: [
files('../tools/run_mpi_test.py'),
'--launcher', mpi_launcher,
'--processes', '2',
parallel_example,
],
env: runtime_environment,
timeout: 180,
)
endif
endif
if get_option('build_tests') and get_option('build_python')
python_test_environment = environment()
python_test_environment.prepend('PYTHONPATH', python_extension_dir)
if mfem_runtime_prefix != ''
if host_machine.system() == 'darwin'
python_test_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
python_test_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
elif host_machine.system() != 'windows'
python_test_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
python_test_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
endif
endif
test(
'python-nanobind',
python_build,
args: [
'-c',
'import _core; assert _core.serial_poisson_dofs(3, 1) > 0; assert _core.capabilities()["hypre"] == _core.capabilities()["mpi"]',
],
depends: python_extension,
env: python_test_environment,
timeout: 120,
)
endif

2387
tools/build_mfem_bundle.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2017-2022, Lawrence Livermore National Security, LLC and
// other CEED contributors. All Rights Reserved.
// SPDX-License-Identifier: BSD-2-Clause
// Relocatable replacement for libCEED's installed JIT source-root object.
// Upstream normally compiles the installation prefix into libceed. Native
// source bundles here are shared libraries, so derive ../include from the
// loaded library instead. Emscripten has no runtime compiler/JIT filesystem.
#if defined(__EMSCRIPTEN__)
const char *CeedJitSourceRootDefault = "";
#else
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <dlfcn.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
static char ceed_jit_source_root[PATH_MAX];
const char *CeedJitSourceRootDefault = ceed_jit_source_root;
__attribute__((constructor)) static void CeedSetRelocatableJitSourceRoot(void) {
Dl_info info;
char library_path[PATH_MAX];
if (!dladdr((void *)&CeedJitSourceRootDefault, &info) || !info.dli_fname) {
return;
}
if (snprintf(library_path, sizeof library_path, "%s", info.dli_fname) < 0) {
return;
}
char *separator = strrchr(library_path, '/');
if (!separator) {
return;
}
*separator = '\0';
(void)snprintf(ceed_jit_source_root, sizeof ceed_jit_source_root,
"%s/../include/", library_path);
}
#endif

39
tools/install_bundle.py Normal file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Copy a build-tree MFEM prefix into a Meson/meson-python destination."""
from __future__ import annotations
import argparse
import os
from pathlib import Path
import shutil
def staged_destination(destination: Path) -> Path:
destdir = os.environ.get("DESTDIR", "")
if not destdir or not destination.is_absolute():
return destination
destination_text = os.fspath(destination)
relative = destination_text[len(destination.anchor) :].lstrip("/\\")
return Path(destdir) / relative
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source", type=Path, required=True)
parser.add_argument("--destination", type=Path, required=True)
args = parser.parse_args()
destination = staged_destination(args.destination)
destination.mkdir(parents=True, exist_ok=True)
for child in args.source.iterdir():
target = destination / child.name
if child.is_dir():
shutil.copytree(child, target, dirs_exist_ok=True, symlinks=True)
else:
shutil.copy2(child, target, follow_symlinks=False)
print(f"Installed MFEM native bundle to {destination}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

33
tools/run_mpi_test.py Normal file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""Run one MPI test without baking a particular MPI launcher into the meson build tree."""
from __future__ import annotations
import argparse
import os
import sys
import subprocess
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--launcher", required=True)
parser.add_argument("--processes", type=int, default=2)
parser.add_argument("executable")
parser.add_argument("arguments", nargs="*")
args = parser.parse_args()
command = [args.launcher, "-n", str(args.processes), args.executable, *args.arguments]
environment = dict(os.environ)
environment.setdefault("OMPI_ALLOW_RUN_AS_ROOT", "1")
environment.setdefault("OMPI_ALLOW_RUN_AS_ROOT_CONFIRM", "1")
loopback = "lo0" if sys.platform == "darwin" else "lo"
environment.setdefault("FI_PROVIDER", "sockets")
environment.setdefault("FI_SOCKETS_IFACE", loopback)
environment.setdefault("HYDRA_IFACE", loopback)
if sys.platform == "darwin":
environment.setdefault("MPICH_INTERFACE_HOSTNAME", "127.0.0.1")
return subprocess.run(command, env=environment, check=False).returncode
if __name__ == "__main__":
raise SystemExit(main())

66
tools/validate_matrix.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/sh
set -eu
project_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
meson_bin=${MESON_BIN:-meson}
meson_dir=$(CDPATH= cd -- "$(dirname -- "$meson_bin")" && pwd)
profile=${MFEM_MATRIX_PROFILE:-portable}
jobs=${MFEM_JOBS:-4}
compiler_family() {
"$1" --version 2>&1 | sed -n '1p'
}
require_clang() {
version=$(compiler_family "$1")
case "$version" in
*clang*|*Clang*) ;;
*) echo "error: $1 is not a Clang compiler: $version" >&2; exit 2 ;;
esac
}
require_gnu() {
version=$(compiler_family "$1")
case "$version" in
*gcc*|*GCC*|*"Free Software Foundation"*) ;;
*) echo "error: $1 is not a GNU compiler: $version" >&2; exit 2 ;;
esac
}
run_configuration() {
name=$1
c_compiler=$2
cxx_compiler=$3
mode=$4
build_dir="$project_root/build-$name"
if test -f "$build_dir/meson-private/coredata.dat"; then
wipe_flag=--wipe
else
wipe_flag=
fi
env PATH="$meson_dir:$PATH" CC="$c_compiler" CXX="$cxx_compiler" \
"$meson_bin" setup $wipe_flag "$build_dir" "$project_root" \
--buildtype="$mode" \
-Dallow_preinstalled=false \
-Dfeature_profile="$profile" \
-Dbuild_examples=true \
-Dbuild_tests=true \
-Dbuild_python=false \
-Djobs="$jobs"
env PATH="$meson_dir:$PATH" "$meson_bin" compile -C "$build_dir"
env PATH="$meson_dir:$PATH" "$meson_bin" test -C "$build_dir" --print-errorlogs
}
clang_cc=${CLANG_CC:-clang}
clang_cxx=${CLANG_CXX:-clang++}
gcc_cc=${GNU_CC:-gcc}
gcc_cxx=${GNU_CXX:-g++}
require_clang "$clang_cxx"
require_gnu "$gcc_cxx"
run_configuration clang-debug "$clang_cc" "$clang_cxx" debug
run_configuration clang-release "$clang_cc" "$clang_cxx" release
run_configuration gcc-debug "$gcc_cc" "$gcc_cxx" debug
run_configuration gcc-release "$gcc_cc" "$gcc_cxx" release