Skip to content
286 changes: 235 additions & 51 deletions elmer.rb
Original file line number Diff line number Diff line change
@@ -1,70 +1,254 @@
class Elmer < Formula
desc """
Elmer finite element solver
desc "Open source multiphysical simulation software (finite element solver)"
homepage "https://elmerfem.org"

* Requires homebrew/science to be tapped for MUMPS.
* OCC, VTK and QWT (for convergence plot) are not supported
"""
homepage "http://elmerfem.org"

head "https://github.com/ElmerCSC/elmerfem.git", :branch => "devel"
head "https://github.com/ElmerCSC/elmerfem.git", branch: "devel"

stable do
url "https://github.com/ElmerCSC/elmerfem/archive/release-8.2.tar.gz"
sha256 "ed4c87895c76003dd81faa464b6d0f38225d43e584f75290df21df629d0a4ecc"
url "https://github.com/ElmerCSC/elmerfem/archive/refs/tags/release-26.2.tar.gz"
sha256 "def442937d69234f7e1b36e902a7fcd2a428d671e62f0275bf05aeef7ebbcade"
end

option "with-elmerice", "Build ElmerIce"
option "with-elmergui", "Build ElmerGUI"
option "with-openmp", "Enable OpenMP support (experimental)"
option "with-testing", "Run the quick tests"
# =============================================================================
# Build Options
# =============================================================================
option "with-elmerice", "Build ElmerIce glaciology module"
option "with-elmergui", "Build ElmerGUI graphical interface"
option "with-gcc", "Use GCC instead of Clang for C/C++ compilation"
option "with-mumps", "Build with the MUMPS sparse direct solver (requires the brewsci/num tap)"
option "with-openmp", "Build with OpenMP support"
option "with-testing", "Run the quick tests after build"

depends_on "open-mpi" => [:f90, :recommended]
# =============================================================================
# Dependencies
# =============================================================================

# Build dependencies
depends_on "cmake" => :build
depends_on "gcc" => ["10", :build]

# Required: Fortran compiler (always needed regardless of C/C++ compiler choice)
depends_on "gcc"

# Required: Linear algebra
depends_on "openblas"
# depends_on "scalapack"
depends_on "hypre" => :recommended
depends_on "mumps" => :recommended

depends_on "qt5" if build.with? "elmergui"
# depends_on "oce" if build.with? "elmergui"
# depends_on "vtk" => "with-qt" if build.with? "elmergui"
# depends_on "qwt" if build.with? "elmergui"
# Optional: Solver libraries
depends_on "hypre" => :optional
# MUMPS is no longer in homebrew-core; it lives in the brewsci/num tap. Pull it
# in only when requested so the formula still loads without that tap tapped.
depends_on "brewsci/num/brewsci-mumps" if build.with?("mumps")

# Optional: Parallelization
depends_on "libomp" => :optional
depends_on "open-mpi" => :optional
Comment thread
kdunn926 marked this conversation as resolved.

# Optional: GUI dependencies
depends_on "opencascade" => :optional
depends_on "qt" => :optional
depends_on "qwt" => :optional
depends_on "vtk" => :optional

def install
cmake_args = %W[-DCMAKE_INSTALL_PREFIX=#{prefix}]
cmake_args << "-DWITH_Hypre:BOOL=TRUE" if build.with? "hypre"
cmake_args << "-DWITH_ElmerIce:BOOL=TRUE" if build.with? "elmerice"
cmake_args << "-DWITH_Mumps:BOOL=TRUE" if build.with? "mumps"
cmake_args << "-DWITH_MPI:BOOL=FALSE" if build.without? "open-mpi"
cmake_args << "-DWITH_MPI:BOOL=TRUE" if build.with? "open-mpi"
cmake_args << "-DWITH_OpenMP:BOOL=TRUE" if build.with? "openmp"

exten = (OS.mac?) ? "dylib" : "so"
cmake_args << "-DBLAS_LIBRARIES:STRING=#{Formula["openblas"].opt_lib}/libopenblas.#{exten};-lpthread"
cmake_args << "-DLAPACK_LIBRARIES:STRING=#{Formula["openblas"].opt_lib}/libopenblas.#{exten};-lpthread"

if build.with? "elmergui"
cmake_args << "-DWITH_ELMERGUI:BOOL=TRUE"
# cmake_args << "-DWITH_QWT:BOOL=TRUE"
# cmake_args << "-DWITH_OCC:BOOL=TRUE"
# cmake_args << "-DWITH_VTK:BOOL=TRUE"
cmake_args << "-DQWT_INCLUDE_DIR=#{Formula["qwt"].lib}/qwt.framework/Headers"
cmake_args << "-DWITH_QT5:BOOL=TRUE"
# Determine CMake binary
cmake_bin = Formula["cmake"].opt_bin/"cmake"
ctest_bin = Formula["cmake"].opt_bin/"ctest"

# Compiler configuration
gcc_formula_str = "gcc"
gcc_formula = Formula[gcc_formula_str]
gcc_version = gcc_formula.version.major
use_gcc = build.with?("gcc")

# SDK configuration
sdk_path = MacOS.sdk_path
sdk_version = Utils.safe_popen_read("xcrun", "--show-sdk-version").strip

if build.head? && sdk_version < "15.5"
odie "Homebrew GCC requires macOS SDK 15.5 or newer (found #{sdk_version})"
end

mkdir "build" do
system "cmake -DCMAKE_C_COMPILER=/usr/local/bin/gcc -DCMAKE_CXX_COMPILER=/usr/local/bin/g++", "../", *cmake_args, *std_cmake_args
system "make"
system "make", "install"
system "ctest -L quick" if build.with? "testing"
# Build sysroot flags
sys_root = use_gcc ? "--sysroot=#{sdk_path}" : "-isysroot #{sdk_path}"

# Ensure the compiler is available
unless gcc_formula.any_version_installed?
odie "Elmer version requires #{gcc_formula_str}. Run: brew install #{gcc_formula_str}"
end

# Compiler flags
c_flags = "#{sys_root} -Wno-error=implicit-function-declaration -Wno-implicit-function-declaration"
cxx_flags = sys_root
cxx_flags += " -Wno-deprecated-declarations" if use_gcc
fortran_flags = ""

# =============================================================================
# CMake Arguments
# =============================================================================
cmake_args = std_cmake_args.dup

# Compiler selection
if use_gcc
cmake_args << "-DCMAKE_C_COMPILER=#{gcc_formula.opt_bin}/gcc-#{gcc_version}"
cmake_args << "-DCMAKE_CXX_COMPILER=#{gcc_formula.opt_bin}/g++-#{gcc_version}"
else
cmake_args << "-DCMAKE_C_COMPILER=/usr/bin/clang"
cmake_args << "-DCMAKE_CXX_COMPILER=/usr/bin/clang++"
end

# Fortran always uses gfortran
cmake_args << "-DCMAKE_Fortran_COMPILER=#{gcc_formula.opt_bin}/gfortran-#{gcc_version}"

# Linear algebra
blas_lib = Formula["openblas"].opt_lib/shared_library("libopenblas")
cmake_args << "-DBLAS_LIBRARIES:STRING=#{blas_lib};-lpthread"
cmake_args << "-DLAPACK_LIBRARIES:STRING=#{blas_lib};-lpthread"

# Optional solver features
cmake_args << "-DWITH_ElmerIce=ON" if build.with?("elmerice")

# NOTE: the 3 mgdyn_airgap2 tests (HYPRE BiCGStab + BoomerAMG block
# preconditioning) abort with MPI_ABORT/Errorcode -1. This reproduces with
# both HYPRE 2.33.0 and 3.1.0 and with either Accelerate or OpenBLAS, so it
# is an upstream Elmer/HYPRE issue, not a build-configuration problem.
cmake_args << "-DWITH_Hypre=ON" if build.with?("hypre")

configure_mumps(cmake_args) if build.with?("mumps")

cmake_args << "-DWITH_MPI=#{build.with?("open-mpi") ? "ON" : "OFF"}"
Comment thread
kdunn926 marked this conversation as resolved.

# OpenMP configuration
if build.with?("openmp")
cmake_args << "-DWITH_OpenMP=ON"
cmake_args << "-DWITH_CHOLMOD=ON"

if use_gcc
# GCC has built-in OpenMP support
%w[C CXX Fortran].each do |lang|
cmake_args << "-DOpenMP_#{lang}_FLAGS=-fopenmp"
end
else
# Clang requires libomp
libomp = Formula["libomp"]
cmake_args << "-DOpenMP_ROOT=#{libomp.opt_prefix}"
ENV.append "LDFLAGS", "-L#{libomp.opt_lib} -lomp"
c_flags += " -I#{libomp.opt_include}"
cxx_flags += " -I#{libomp.opt_include}"
end
end

# =============================================================================
# ElmerGUI Configuration
# =============================================================================
configure_elmergui(cmake_args, use_gcc) if build.with?("elmergui")


# SDK and flags
cmake_args << "-DCMAKE_OSX_SYSROOT=#{sdk_path}"
cmake_args << "-DCMAKE_C_FLAGS=#{c_flags}"
cmake_args << "-DCMAKE_CXX_FLAGS=#{cxx_flags}"
cmake_args << "-DCMAKE_Fortran_FLAGS=#{fortran_flags.strip}" unless fortran_flags.strip.empty?

cmake_args << "-DBUILD_TESTING=1" if build.with?("testing")

# Build and install
system cmake_bin, "-S", ".", "-B", "build", *cmake_args
system cmake_bin, "--build", "build", "--parallel"
system cmake_bin, "--install", "build"

# Optionally run the upstream "quick" test suite. Failures are reported but
# do NOT abort the install, so a fully-built keg is always produced. The
# suite drives the build-tree ElmerSolver (via mpiexec when MPI is enabled),
# so a broken host MPI or a single flaky case should not discard the build.
if build.with?("testing")
Dir.chdir("build") do
ohai "Running quick test suite (ctest -L quick)"
begin
system ctest_bin, ".", "-L", "quick", "--output-on-failure"
rescue BuildError
opoo "Some quick tests failed (see output above); installation continues."
end
end
end
end

def configure_mumps(cmake_args)
cmake_args << "-DWITH_Mumps=ON"
cmake_args << "-DMUMPS_ROOT=#{Formula["brewsci/num/brewsci-mumps"].opt_prefix}"
# brewsci-mumps is built with ScaLAPACK ordering plus (Par)Metis, so Elmer's
# FindMumps also requires ScaLAPACK, ParMetis and Metis. Point each finder at
# the right keg through the *_ROOT environment hints they consult. Note metis.h
# lives in brewsci-metis (not brewsci-parmetis), so METIS_ROOT is set separately.
ENV["SCALAPACK_ROOT"] = Formula["scalapack"].opt_prefix.to_s
ENV["PARMETIS_ROOT"] = Formula["brewsci/num/brewsci-parmetis"].opt_prefix.to_s
ENV["METIS_ROOT"] = Formula["brewsci/num/brewsci-metis"].opt_prefix.to_s
end

def configure_elmergui(cmake_args, use_gcc)
cmake_args << "-DWITH_ELMERGUI=ON"

if use_gcc
# Homebrew's Qt6 is built with Clang/libc++ and exports APIs taking std:: types
# (e.g. QDir::mkdir(std::optional<...>)) only with libc++ mangling, which
# GCC/libstdc++ never emits -- so a GCC ElmerGUI cannot link against Qt6. Qt5's
# API surface does not cross that std:: ABI boundary, so GCC links Qt5 cleanly.
# This is how the branch built ElmerGUI with GCC prior to the Qt6-everywhere change.
qt5_dep = "qt@5"
qwt_dep = "qwt-qt5"
dep_message = ->(p) { "ElmerGUI with --with-gcc requires #{p}. To install: brew install #{p}" }
odie dep_message.call(qt5_dep) unless Formula[qt5_dep].any_version_installed?
odie dep_message.call(qwt_dep) unless Formula[qwt_dep].any_version_installed?

cmake_args << "-DWITH_QT5=ON"
qt5_lib = Formula[qt5_dep].opt_lib
cmake_args << "-DQt5_DIR=#{qt5_lib}/cmake/Qt5"
# ElmerGUI FIND_PACKAGEs each Qt5 component; qt@5 is keg-only so point each
# component at its config dir explicitly (mirrors the Qt6 branch below).
%w[Core Gui Widgets OpenGL Xml Svg PrintSupport Script].each do |mod|
cmake_args << "-DQt5#{mod}_DIR=#{qt5_lib}/cmake/Qt5#{mod}"
end

# ElmerGUI's Qt5 package list only includes Qt5Widgets on WIN32; on macOS it is
# omitted, but the Application needs it (QT5_WRAP_UI). Add it to the list.
inreplace "ElmerGUI/CMakeLists.txt",
"SET(QT5_PKG_LIST Qt5OpenGL Qt5Xml Qt5Script Qt5Gui Qt5Core Qt5Svg Qt5PrintSupport)",
"SET(QT5_PKG_LIST Qt5OpenGL Qt5Xml Qt5Script Qt5Gui Qt5Core Qt5Svg Qt5Widgets Qt5PrintSupport)"
Comment on lines +205 to +209

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker for this change: It might make sense to fix that upstream in the elmerfem repository.

else
qwt_dep = "qwt"
cmake_args << "-DWITH_QT6=ON"
qt_lib = Formula["qtbase"].opt_lib
cmake_args << "-DQt6_DIR=#{qt_lib}/cmake/Qt6"
%w[Xml PrintSupport OpenGL OpenGLWidgets].each do |mod|
cmake_args << "-DQt6#{mod}_DIR=#{qt_lib}/cmake/Qt6#{mod}"
end
end

# Qwt configuration (qwt for Qt6, qwt-qt5 for Qt5)
qwt_formula = Formula[qwt_dep]
cmake_args << "-DWITH_QWT=ON"
cmake_args << "-DQWT_INCLUDE_DIR=#{qwt_formula.opt_lib}/qwt.framework/Headers"
cmake_args << "-DQWT_LIBRARY=#{qwt_formula.opt_lib}/qwt.framework/qwt"

# Optional GUI features
cmake_args << "-DWITH_OCC=#{build.with?("opencascade") ? "ON" : "OFF"}"
cmake_args << "-DWITH_VTK=#{build.with?("vtk") ? "ON" : "OFF"}"
end

def caveats
return if build.without?("elmergui")

<<~EOS
If ElmerGUI fails to run with the following error message:

qt.qpa.plugin: Could not find the Qt platform plugin "cocoa" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Try setting the following environment variable (or add the export to ~/.bash_profile):
export QT_QPA_PLATFORM_PLUGIN_PATH=$(brew --prefix qtbase)/share/qt/plugins/platforms
EOS
end

test do
(testpath / "test.sif").write <<-EOS.undent
(testpath/"test.sif").write <<~EOS
Header
CHECK KEYWORDS Warn
Mesh DB "." "geomstiff"
Expand Down Expand Up @@ -125,7 +309,7 @@ def install
Solver 2 :: Reference Norm Tolerance = Real 1e-3
EOS

(testpath / "geomstiff.grd").write <<-EOS.undent
(testpath/"geomstiff.grd").write <<~EOS
##### ElmerGrid input file for structured grid generation ######
Version = 210903
Coordinate System = Cartesian 2D
Expand All @@ -152,7 +336,7 @@ def install
Element Densities 2 = 1
EOS

system "ElmerGrid", "1", "2", "geomstiff.grd"
system "ElmerSolver", "test.sif"
system bin/"ElmerGrid", "1", "2", "geomstiff.grd"
system bin/"ElmerSolver", "test.sif"
end
end