Skip to content
297 changes: 246 additions & 51 deletions elmer.rb
Original file line number Diff line number Diff line change
@@ -1,70 +1,265 @@
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"

# CMake 3.19 for compatibility with old CHECK_TYPE_SIZE syntax
resource "cmake" do
url "https://github.com/Kitware/CMake/releases/download/v3.19.8/cmake-3.19.8-macos-universal.tar.gz"
sha256 "0976d23d982af05dcbfb3aa34fcb62ead43bea27f0e3bb95222f2a78161423f2"
Comment thread
kdunn926 marked this conversation as resolved.
Outdated
end
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-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
if build.stable?
resource("cmake").stage do
(buildpath/"local_cmake").install "CMake.app/Contents"
end
cmake_bin = buildpath/"local_cmake/Contents/bin/cmake"
ctest_bin = buildpath/"local_cmake/Contents/bin/ctest"
else
cmake_bin = Formula["cmake"].opt_bin/"cmake"
ctest_bin = Formula["cmake"].opt_bin/"ctest"
end

# 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 build.head? && use_gcc
Comment thread
kdunn926 marked this conversation as resolved.
Outdated

# =============================================================================
# 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")
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}"

if build.stable?
cmake_args << "-DOpenMP_C_FLAGS=-Xpreprocessor -fopenmp"
cmake_args << "-DOpenMP_CXX_FLAGS=-Xpreprocessor -fopenmp"
cmake_args << "-DOpenMP_Fortran_FLAGS=-fopenmp"
end
Comment thread
kdunn926 marked this conversation as resolved.
Outdated
end
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"
# =============================================================================
# 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}"

# Apply GCC/Qt compatibility patch if needed
if build.head? && use_gcc && build.with?("elmergui")
Comment thread
kdunn926 marked this conversation as resolved.
Outdated
apply_gcc_qt6_patch
Comment thread
kdunn926 marked this conversation as resolved.
Outdated
end

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"

if build.head? && qt_version >= 6
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
else
# stable build needs Qt5
Comment thread
kdunn926 marked this conversation as resolved.
Outdated
qt5_dep = "qt@5"
qwt_dep ="qwt-qt5"

qt5_installed = Formula[qt5_dep].any_version_installed?
qwt_installed = Formula[qwt_dep].any_version_installed?

dep_message = ->(p) { "ElmerGUI requires #{p}. To install: brew install #{p}" }
odie dep_message.call(qt5_dep) unless qt5_installed
odie dep_message.call(qwt_dep) unless qwt_installed

cmake_args << "-DWITH_QT5=ON"
cmake_args << "-DQt5_DIR=#{Formula[qt5_dep].opt_lib}/cmake/Qt5"
end
Comment thread
kdunn926 marked this conversation as resolved.
Outdated

# 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 apply_gcc_qt6_patch
Comment thread
kdunn926 marked this conversation as resolved.
Outdated
patch_content = <<~PATCH
--- ElmerGUI/CMakeLists.txt
+++ ElmerGUI/CMakeLists.txt
@@ -35,6 +35,10 @@
FOREACH(_pkg ${QT6_PKG_LIST})
FIND_PACKAGE(${_pkg} PATHS ${QT6_PATH} REQUIRED)
ENDFOREACH()
+
+ IF(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D'QT_IGNORE_DEPRECATIONS\\(x\\)=x'")
+ ENDIF()

ADD_DEFINITIONS(-DWITH_QT6)
MESSAGE(STATUS " [ElmerGUI] Qt6: " ${Qt6_FOUND})
PATCH

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If adding the flag in the formula works (see other suggestion), you can probably get rid of this patch.

Suggested change
def apply_gcc_qt6_patch
patch_content = <<~PATCH
--- ElmerGUI/CMakeLists.txt
+++ ElmerGUI/CMakeLists.txt
@@ -35,6 +35,10 @@
FOREACH(_pkg ${QT6_PKG_LIST})
FIND_PACKAGE(${_pkg} PATHS ${QT6_PATH} REQUIRED)
ENDFOREACH()
+
+ IF(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D'QT_IGNORE_DEPRECATIONS\\(x\\)=x'")
+ ENDIF()
ADD_DEFINITIONS(-DWITH_QT6)
MESSAGE(STATUS " [ElmerGUI] Qt6: " ${Qt6_FOUND})
PATCH

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Oops. Missed a couple of lines further down that should also be removed in that case.
I don't know how to change that now though...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thinking about the condition for which the patch is applied: Was it maybe only needed for (very?) old GCC versions?
Does it still fail to build without this patch with more or less recent versions of GCC?
If it fails, which error does that produce?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In my testing, I found the patch (or your cleaner cxx_flags workaround) is needed even with gcc 16.1.0. That said, I could only get gcc to link against Qt5, not Qt6, which is at least one good reason to keep the Qt5 stuff in the formula around.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good point. 👍

Is defining that preprocessor macros only needed for GCC? Or does Qt5 also need it with Clang?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Never mind.
The build rules here make clear that GCC selects Qt5 and Clang selects Qt6. So, the combination I asked for is never an option for the user.

More basically: Why is there even an option to build ElmerFEM with GCC on macOS? Is that preferable in some ways compared to using their "system" compiler Clang?


(buildpath/"qt-gcc.patch").write(patch_content)
system "patch", "-p0", "-i", "qt-gcc.patch"
end

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

<<~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 +320,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 +347,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