Skip to content
241 changes: 190 additions & 51 deletions elmer.rb
Original file line number Diff line number Diff line change
@@ -1,70 +1,209 @@
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-accelerate", "Build with Apple Accelerate support"
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-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
depends_on "mumps" => :optional

# 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

# Build sysroot flags
sys_root = use_gcc ? "--sysroot=#{sdk_path}" : "-isysroot #{sdk_path}"

# For stable builds with GCC, try to find an older SDK if available
if build.stable? && use_gcc
older_sdk = Dir["/Library/Developer/CommandLineTools/SDKs/MacOSX1[0-4].sdk"].max
if older_sdk
sys_root = "--sysroot=#{older_sdk}"
sdk_path = older_sdk
else
opoo "No older macOS SDK found; GCC build may have compatibility issues"
end

unless gcc_formula.any_version_installed?
odie "Elmer version requires #{gcc_formula_str}. Run: brew install #{gcc_formula_str}"
end
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

# =============================================================================
# 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

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"
# Fortran always uses gfortran
cmake_args << "-DCMAKE_Fortran_COMPILER=#{gcc_formula.opt_bin}/gfortran-#{gcc_version}"

# Linear algebra
if build.with?("accelerate")
cmake_args << "-DBLAS_LIBRARIES:STRING=-framework Accelerate"
cmake_args << "-DLAPACK_LIBRARIES:STRING=-framework Accelerate"
else
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"
end

# Optional solver features
cmake_args << "-DWITH_ElmerIce=ON" if build.with?("elmerice")
cmake_args << "-DWITH_Hypre=ON" if build.with?("hypre")
cmake_args << "-DWITH_Mumps=ON" 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) 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 << "-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"
Dir.chdir("build") do
system ctest_bin, ".", "-L", "quick" if build.with?("testing")
end
end

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

# Qt version detection
qt_formula = Formula["qt"]
qt_version = qt_formula.version.major.to_i
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

# Qwt configuration
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 +264,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 +291,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