diff --git a/.github/workflows/deploy-tap.yml b/.github/workflows/deploy-tap.yml
new file mode 100644
index 0000000000..2075d26e6c
--- /dev/null
+++ b/.github/workflows/deploy-tap.yml
@@ -0,0 +1,102 @@
+name: Deploy Homebrew Tap
+
+on:
+ push:
+ branches: [ main, master, homebrew-formula ]
+ paths:
+ - 'packaging/homebrew/mfc.rb'
+ - 'packaging/homebrew/README.md'
+ tags:
+ - 'v*.*.*'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ deploy-tap:
+ name: Sync/bump formula in tap
+ runs-on: macos-14
+ permissions:
+ contents: write
+ pull-requests: write
+ steps:
+ - name: Checkout MFC repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Determine event metadata
+ id: meta
+ run: |
+ if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
+ VERSION="${GITHUB_REF_NAME#v}"
+ URL="https://github.com/${{ github.repository }}/archive/refs/tags/v${VERSION}.tar.gz"
+ else
+ # Extract URL from current formula to re-audit and sync
+ URL="$(grep -Eo 'https://github.com/.*/archive/refs/tags/v[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz' packaging/homebrew/mfc.rb | head -n1)"
+ VERSION="$(echo "${URL}" | sed -E 's/.*v([0-9]+\.[0-9]+\.[0-9]+)\.tar\.gz/\1/')"
+ fi
+ SHASUM="$(curl -sL "${URL}" | shasum -a 256 | awk '{print $1}')"
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "url=${URL}" >> $GITHUB_OUTPUT
+ echo "sha256=${SHASUM}" >> $GITHUB_OUTPUT
+
+ - name: Update formula (for tag events)
+ if: github.ref_type == 'tag'
+ run: |
+ /usr/bin/sed -i '' "s@^ url \".*\"@ url \"${{ steps.meta.outputs.url }}\"@" packaging/homebrew/mfc.rb
+ /usr/bin/sed -i '' "s@^ sha256 \".*\"@ sha256 \"${{ steps.meta.outputs.sha256 }}\"@" packaging/homebrew/mfc.rb
+
+ - name: Setup Homebrew
+ uses: Homebrew/actions/setup-homebrew@master
+
+ - name: Audit/style formula before pushing
+ run: |
+ brew style packaging/homebrew/mfc.rb
+ # Create temporary tap to audit the formula
+ brew tap-new mfc/local
+ cp packaging/homebrew/mfc.rb "$(brew --repository)/Library/Taps/mfc/homebrew-local/Formula/mfc.rb"
+ brew audit --online --strict --new --except=homepage mfc/local/mfc || brew audit --online mfc/local/mfc
+ brew untap mfc/local
+
+ - name: Clone or bootstrap tap repository
+ env:
+ TAP_TOKEN: ${{ secrets.TAP_REPO_TOKEN }}
+ run: |
+ set -euo pipefail
+ REPO="https://x-access-token:${TAP_TOKEN}@github.com/MFlowCode/homebrew-mfc.git"
+ if git ls-remote "${REPO}" HEAD >/dev/null 2>&1; then
+ git clone "${REPO}" tap-repo
+ else
+ # Repo exists but might be empty; fall back to bootstrap
+ mkdir -p tap-repo
+ cd tap-repo
+ git init -b main
+ git remote add origin "${REPO}"
+ touch .keep
+ git add .keep
+ git -c user.name="github-actions[bot]" -c user.email="github-actions[bot]@users.noreply.github.com" commit -m "Initialize tap"
+ git push -u origin main
+ fi
+
+ - name: Copy formula and README into tap
+ run: |
+ mkdir -p tap-repo/Formula
+ cp packaging/homebrew/mfc.rb tap-repo/Formula/mfc.rb
+ cp packaging/homebrew/README.md tap-repo/README.md
+
+ - name: Commit & push if changed
+ env:
+ TAP_TOKEN: ${{ secrets.TAP_REPO_TOKEN }}
+ run: |
+ cd tap-repo
+ git add Formula/mfc.rb README.md
+ if git diff --cached --quiet; then
+ echo "No changes in Formula/mfc.rb or README.md; skipping push."
+ exit 0
+ fi
+ git -c user.name="github-actions[bot]" -c user.email="github-actions[bot]@users.noreply.github.com" \
+ commit -m "mfc: v${{ steps.meta.outputs.version }}"
+ git push origin HEAD:main
+
diff --git a/.github/workflows/homebrew.yml b/.github/workflows/homebrew.yml
new file mode 100644
index 0000000000..3dfffea1e7
--- /dev/null
+++ b/.github/workflows/homebrew.yml
@@ -0,0 +1,264 @@
+name: Homebrew Formula Test
+
+on:
+ push:
+ branches:
+ - master
+ - homebrew-formula
+ paths:
+ - 'packaging/homebrew/**'
+ - '.github/workflows/homebrew.yml'
+ pull_request:
+ branches:
+ - master
+ paths:
+ - 'packaging/homebrew/**'
+ - '.github/workflows/homebrew.yml'
+ workflow_dispatch:
+
+jobs:
+ # Fast smoke tests that run before expensive operations
+ smoke-test:
+ name: Quick Formula Validation
+ runs-on: ubuntu-latest # Use Linux for speed (Homebrew works on Linux too)
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Homebrew
+ uses: Homebrew/actions/setup-homebrew@master
+
+ - name: Validate formula syntax with brew style
+ run: |
+ echo "Checking formula syntax..."
+ brew style packaging/homebrew/mfc.rb
+
+ - name: Run brew audit (without installation)
+ run: |
+ echo "Configuring git for brew tap-new..."
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+
+ echo "Creating temporary local tap..."
+ brew tap-new mflowcode/test
+ cp packaging/homebrew/mfc.rb $(brew --repository)/Library/Taps/mflowcode/homebrew-test/Formula/mfc.rb
+
+ echo "Running brew audit (online checks)..."
+ brew audit --online --skip-style mflowcode/test/mfc || true
+
+ echo "Cleaning up tap..."
+ brew untap mflowcode/test
+
+ - name: Validate Ruby syntax
+ run: |
+ echo "Checking Ruby syntax..."
+ ruby -c packaging/homebrew/mfc.rb
+
+ - name: Check for common formula issues
+ run: |
+ echo "Checking for common issues..."
+
+ # Check that required fields are present
+ grep -q 'desc "' packaging/homebrew/mfc.rb || (echo "❌ Missing desc"; exit 1)
+ grep -q 'homepage "' packaging/homebrew/mfc.rb || (echo "❌ Missing homepage"; exit 1)
+ grep -q 'url "' packaging/homebrew/mfc.rb || (echo "❌ Missing url"; exit 1)
+ grep -q 'sha256 "' packaging/homebrew/mfc.rb || (echo "❌ Missing sha256"; exit 1)
+ grep -q 'license "' packaging/homebrew/mfc.rb || (echo "❌ Missing license"; exit 1)
+
+ # Check that install method exists
+ grep -q 'def install' packaging/homebrew/mfc.rb || (echo "❌ Missing install method"; exit 1)
+
+ # Check that test block exists
+ grep -q 'test do' packaging/homebrew/mfc.rb || (echo "❌ Missing test block"; exit 1)
+
+ echo "✅ All required formula components present"
+
+ - name: Verify URL is reachable
+ run: |
+ echo "Checking that source URL is reachable..."
+ URL=$(grep -E 'url "https://[^"]+' packaging/homebrew/mfc.rb | head -1 | sed 's/.*url "\([^"]*\)".*/\1/')
+
+ if [ -z "$URL" ]; then
+ echo "❌ Could not extract URL from formula"
+ exit 1
+ fi
+
+ echo "URL: $URL"
+ HTTP_CODE=$(curl -sI -w "%{http_code}" -o /dev/null "$URL")
+
+ if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ]; then
+ echo "✅ URL is reachable (HTTP $HTTP_CODE)"
+ else
+ echo "⚠️ URL returned HTTP $HTTP_CODE (may indicate an issue)"
+ # Don't fail here - could be a temporary issue
+ fi
+
+ - name: Verify SHA256 checksum
+ run: |
+ echo "Verifying SHA256 checksum matches URL..."
+ URL=$(grep -E 'url "https://[^"]+' packaging/homebrew/mfc.rb | head -1 | sed 's/.*url "\([^"]*\)".*/\1/')
+ EXPECTED_SHA=$(grep 'sha256 "' packaging/homebrew/mfc.rb | head -1 | sed 's/.*sha256 "\([^"]*\)".*/\1/')
+
+ if [ -z "$URL" ] || [ -z "$EXPECTED_SHA" ]; then
+ echo "❌ Could not extract URL or SHA256 from formula"
+ exit 1
+ fi
+
+ echo "Downloading tarball to compute checksum..."
+ ACTUAL_SHA=$(curl -sL "$URL" | shasum -a 256 | awk '{print $1}')
+
+ echo "Expected SHA256: $EXPECTED_SHA"
+ echo "Actual SHA256: $ACTUAL_SHA"
+
+ if [ "$EXPECTED_SHA" = "$ACTUAL_SHA" ]; then
+ echo "✅ SHA256 checksum matches!"
+ else
+ echo "❌ SHA256 mismatch!"
+ exit 1
+ fi
+
+ # Full installation test (only runs if smoke tests pass)
+ test-formula:
+ name: Full Installation Test
+ needs: smoke-test # Only run after smoke tests pass
+ runs-on: macos-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Homebrew
+ run: |
+ echo "Homebrew version:"
+ brew --version
+ echo "Updating Homebrew..."
+ brew update
+
+ - name: Install formula dependencies
+ run: |
+ echo "Installing MFC dependencies..."
+ brew install cmake gcc python@3.12 boost fftw hdf5 open-mpi openblas
+
+ - name: Install MFC from formula
+ run: |
+ echo "Creating temporary local tap..."
+ brew tap-new mflowcode/test
+
+ echo "Copying formula to tap..."
+ cp packaging/homebrew/mfc.rb $(brew --repository)/Library/Taps/mflowcode/homebrew-test/Formula/mfc.rb
+
+ echo "Installing MFC from local tap..."
+ # Note: brew may exit with code 1 due to dylib fixup warnings on some Python packages (non-fatal)
+ # We verify installation using brew commands rather than parsing log output
+ set +e # Don't fail immediately on error
+ brew install --build-from-source --verbose mflowcode/test/mfc 2>&1 | tee /tmp/brew-install.log
+ brew_exit_code=$?
+ set -e
+
+ # Verify installation using brew list (more robust than log parsing)
+ if brew list mflowcode/test/mfc &>/dev/null; then
+ echo "✅ MFC installed successfully (ignoring dylib relocation warnings)"
+ # Optionally verify with brew info
+ brew info mflowcode/test/mfc
+ exit 0
+ else
+ echo "❌ MFC installation failed"
+ exit $brew_exit_code
+ fi
+
+ - name: Display error logs on failure
+ if: failure()
+ run: |
+ echo "=== Displaying last 200 lines of brew install log ==="
+ if [ -f /tmp/brew-install.log ]; then
+ tail -200 /tmp/brew-install.log
+ fi
+
+ echo -e "\n=== Displaying Homebrew log files ==="
+ if [ -d ~/Library/Logs/Homebrew/mfc/ ]; then
+ for logfile in ~/Library/Logs/Homebrew/mfc/*; do
+ if [ -f "$logfile" ]; then
+ echo -e "\n\n====== $logfile ======"
+ cat "$logfile"
+ fi
+ done
+ fi
+
+ echo -e "\n=== Searching for Cantera config.log ==="
+ cantera_config_log=$(find /private/tmp -name "config.log" -path "*/mfc--cantera*" 2>/dev/null | head -1)
+ if [ -n "$cantera_config_log" ] && [ -f "$cantera_config_log" ]; then
+ echo -e "\n\n====== Cantera config.log ======"
+ echo "Found at: $cantera_config_log"
+ cat "$cantera_config_log"
+ # Copy to a known location for artifact upload
+ mkdir -p /tmp/cantera-logs
+ cp "$cantera_config_log" /tmp/cantera-logs/config.log
+ else
+ echo "Cantera config.log not found"
+ echo "Searching in all /private/tmp directories:"
+ find /private/tmp -name "config.log" 2>/dev/null || echo "No config.log files found"
+ fi
+
+ - name: Upload Homebrew logs on failure
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: homebrew-logs
+ path: |
+ /tmp/brew-install.log
+ /tmp/cantera-logs/
+ ~/Library/Logs/Homebrew/mfc/
+ if-no-files-found: ignore
+
+ - name: Test MFC installation
+ run: |
+ echo "=== Testing MFC Installation ==="
+
+ echo "1. Checking binaries exist and are executable..."
+ test -f $(brew --prefix)/bin/mfc && test -x $(brew --prefix)/bin/mfc
+ test -f $(brew --prefix)/bin/pre_process && test -x $(brew --prefix)/bin/pre_process
+ test -f $(brew --prefix)/bin/simulation && test -x $(brew --prefix)/bin/simulation
+ test -f $(brew --prefix)/bin/post_process && test -x $(brew --prefix)/bin/post_process
+ echo " ✓ All binaries exist and are executable"
+
+ echo "2. Verifying installation structure..."
+ test -f $(brew --prefix mfc)/libexec/mfc.sh
+ test -d $(brew --prefix mfc)/toolchain
+ echo " ✓ Installation structure verified"
+
+ echo "3. Checking Python venv..."
+ test -d $(brew --prefix mfc)/libexec/venv
+ test -f $(brew --prefix mfc)/libexec/venv/bin/python
+ test -f $(brew --prefix mfc)/libexec/venv/bin/pip
+ echo " ✓ Python venv exists"
+
+ echo "4. Checking examples..."
+ test -d $(brew --prefix mfc)/examples
+ test -f $(brew --prefix mfc)/examples/1D_sodshocktube/case.py
+ echo " ✓ Examples installed"
+
+ echo "5. Testing mfc wrapper..."
+ mfc --help
+ echo " ✓ mfc --help succeeded"
+
+ echo "=== All tests passed! ==="
+
+ - name: Run MFC test case
+ run: |
+ echo "Running a simple test case (1D Sod shock tube)..."
+ TESTDIR=$(mktemp -d)
+ cp $(brew --prefix mfc)/examples/1D_sodshocktube/case.py "$TESTDIR/"
+
+ echo "Running with $(sysctl -n hw.ncpu) processors..."
+ # Use absolute path since mfc wrapper creates its own tmpdir
+ mfc run "$TESTDIR/case.py" -j $(sysctl -n hw.ncpu)
+
+ echo "Test case completed successfully!"
+
+ - name: Uninstall and cleanup
+ if: always()
+ run: |
+ echo "Cleaning up..."
+ brew uninstall mfc || true
+ brew cleanup
diff --git a/.gitignore b/.gitignore
index 30393c710b..a56c6a6749 100644
--- a/.gitignore
+++ b/.gitignore
@@ -86,3 +86,6 @@ benchmarks/*.png
*.mov
*.mkv
*.avi
+
+packaging/spack/spack-test
+.spack
\ No newline at end of file
diff --git a/.typos.toml b/.typos.toml
index 1fb0c90272..a595990e72 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -20,6 +20,7 @@ Strang = "Strang"
TKE = "TKE"
HSA = "HSA"
infp = "infp"
+Sur = "Sur"
[files]
extend-exclude = ["docs/documentation/references*", "tests/", "toolchain/cce_simulation_workgroup_256.sh"]
diff --git a/README.md b/README.md
index aafc401a1e..4d410f8a65 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,7 @@ MFC runs at exascale on the world's fastest supercomputers:
| Path | Command |
| --- | --- |
| **Codespaces** (fastest) | Click the "Codespaces" badge above to launch in 1 click |
+| **Homebrew (macOS)** | `brew install mflowcode/mfc/mfc && mfc run $(brew --prefix mfc)/examples/1D_sodshocktube/case.py -n 2` |
| **Local build** | `./mfc.sh build -j $(nproc) && ./mfc.sh test -j $(nproc)` |
**Welcome!**
@@ -136,7 +137,23 @@ Click <> Code (green button at top right) → Codespaces
You can navigate [to this webpage](https://mflowcode.github.io/documentation/md_getting-started.html) to get you get started using MFC on your local machine, cluster, or supercomputer!
It's rather straightforward.
-We'll give a brief introdocution for MacOS below.
+
+### macOS quick start (Homebrew)
+
+Install the prebuilt package and run an example:
+
+```bash
+brew install mflowcode/mfc/mfc
+mkdir -p ~/mfc_quickstart && cd ~/mfc_quickstart
+cp $(brew --prefix mfc)/examples/1D_sodshocktube/case.py .
+mfc run case.py -n 2
+```
+
+Use `-n X` to select the number of MPI processes. For developer commands (`build`, `test`, etc.), clone the repo and use `./mfc.sh`.
+
+### macOS from source
+
+We'll give a brief introduction for building from source on MacOS below.
Using [brew](https://brew.sh), install MFC's dependencies:
```shell
brew install coreutils python cmake fftw hdf5 gcc boost open-mpi lapack
diff --git a/docs/documentation/getting-started.md b/docs/documentation/getting-started.md
index 9478a72ce2..3494a14b4a 100644
--- a/docs/documentation/getting-started.md
+++ b/docs/documentation/getting-started.md
@@ -9,6 +9,28 @@ git clone https://github.com/MFlowCode/MFC.git
cd MFC
```
+## Install via Homebrew (macOS)
+
+On macOS, install prebuilt MFC via Homebrew:
+
+```bash
+brew install mflowcode/mfc/mfc
+```
+
+Run a quick example:
+
+```bash
+mkdir -p ~/mfc_quickstart && cd ~/mfc_quickstart
+cp $(brew --prefix mfc)/examples/1D_sodshocktube/case.py .
+# Use -n X to choose the number of MPI processes
+mfc run case.py -n 2
+```
+
+Notes:
+- The Homebrew wrapper supports only `mfc run ...`. Developer commands like `build`, `test`, `clean` are available when you clone the repo and use `./mfc.sh`.
+- The package bundles a Python venv and prebuilt binaries; no additional setup is required.
+- Examples are installed at `$(brew --prefix mfc)/examples/`.
+
## Build Environment
MFC can be built in multiple ways on various operating systems.
diff --git a/docs/documentation/running.md b/docs/documentation/running.md
index eb389e4ffb..7438fc5912 100644
--- a/docs/documentation/running.md
+++ b/docs/documentation/running.md
@@ -8,6 +8,20 @@ A full (and up-to-date) list of available arguments can be acquired with `./mfc.
MFC supports running simulations locally (Linux, MacOS, and Windows) as well as
several supercomputer clusters, both interactively and through batch submission.
+## Using the Homebrew package (macOS)
+
+If you installed MFC via Homebrew, run cases with the `mfc` wrapper:
+
+```bash
+mfc run -n 2
+```
+
+- Use `-n X` to control the number of MPI processes (ranks).
+- Only the `run` command is supported in the Homebrew wrapper.
+- To use developer commands (`build`, `test`, `clean`, etc.), clone the repository and use `./mfc.sh`.
+- The wrapper passes through runtime flags like `-t pre_process simulation`, `-n`, and others; it always runs with preinstalled binaries.
+- Examples live at `$(brew --prefix mfc)/examples/`.
+
> [!IMPORTANT]
> Running simulations locally should work out of the box. On supported clusters,
> you can append `-c ` on the command line to instruct the MFC toolchain
diff --git a/packaging/homebrew/README.md b/packaging/homebrew/README.md
new file mode 100644
index 0000000000..dd99d2be7f
--- /dev/null
+++ b/packaging/homebrew/README.md
@@ -0,0 +1,60 @@
+# MFC Homebrew Tap
+
+Official Homebrew tap for [MFC (Multiphase Flow Code)](https://github.com/MFlowCode/MFC) – an exascale multiphase/multiphysics compressible flow solver.
+
+## Installation
+
+```bash
+brew install mflowcode/mfc/mfc
+```
+
+## Quick Start
+
+Run the 1D Sod shock tube example:
+
+```bash
+mkdir -p ~/mfc_example && cd ~/mfc_example
+cp $(brew --prefix mfc)/examples/1D_sodshocktube/case.py .
+mfc run case.py -n 2
+```
+
+## What's Included
+
+- **Prebuilt binaries**: `pre_process`, `simulation`, `post_process`
+- **Python toolchain**: Pre-configured virtual environment with all dependencies
+- **Examples**: Located at `$(brew --prefix mfc)/examples/`
+- **`mfc` wrapper**: Simplified command-line interface for running cases
+
+## Usage
+
+```bash
+mfc run -n
+```
+
+Use `-n X` to set the number of MPI processes.
+
+**Note**: The Homebrew wrapper supports only `mfc run`. For developer commands (`build`, `test`, `clean`, etc.), [clone the repository](https://github.com/MFlowCode/MFC) and use `./mfc.sh`.
+
+## Documentation
+
+- [MFC Documentation](https://mflowcode.github.io/)
+- [Getting Started Guide](https://mflowcode.github.io/documentation/md_getting-started.html)
+- [Running Cases](https://mflowcode.github.io/documentation/md_running.html)
+
+## About This Tap
+
+This tap is automatically maintained by the [MFC repository](https://github.com/MFlowCode/MFC) via GitHub Actions. The formula is synced from [`packaging/homebrew/mfc.rb`](https://github.com/MFlowCode/MFC/blob/master/packaging/homebrew/mfc.rb).
+
+For issues or contributions related to the Homebrew formula, please visit the [main MFC repository](https://github.com/MFlowCode/MFC).
+
+## Uninstall
+
+```bash
+brew uninstall mfc
+brew untap mflowcode/mfc
+```
+
+## License
+
+MFC is licensed under the [MIT License](https://github.com/MFlowCode/MFC/blob/master/LICENSE).
+
diff --git a/packaging/homebrew/mfc.rb b/packaging/homebrew/mfc.rb
new file mode 100644
index 0000000000..0496498303
--- /dev/null
+++ b/packaging/homebrew/mfc.rb
@@ -0,0 +1,275 @@
+# typed: strict
+# frozen_string_literal: true
+
+# Homebrew formula for MFC (Multiphase Flow Code)
+# This formula is automatically deployed to the MFlowCode/homebrew-mfc tap by GitHub Actions workflow
+class Mfc < Formula
+ desc "Exascale multiphase/multiphysics compressible flow solver"
+ homepage "https://mflowcode.github.io/"
+ url "https://github.com/MFlowCode/MFC/archive/refs/tags/v5.1.0.tar.gz"
+ sha256 "4684bee6a529287f243f8929fb7edb0dfebbb04df7c1806459761c9a6c9261cf"
+ license "MIT"
+ head "https://github.com/MFlowCode/MFC.git", branch: "master"
+
+ depends_on "cmake" => :build
+ depends_on "gcc" => :build
+
+ depends_on "boost"
+ depends_on "fftw"
+ depends_on "hdf5"
+ depends_on "open-mpi"
+ depends_on "openblas"
+ depends_on "python@3.12"
+
+ # Skip relocation for Python C extensions in the venv
+ # The venv is self-contained in libexec and doesn't need Homebrew's relocation
+ skip_clean "libexec/venv"
+
+ def install
+ # Create Python virtual environment inside libexec (inside Cellar for proper bottling)
+ venv = libexec/"venv"
+ system Formula["python@3.12"].opt_bin/"python3.12", "-m", "venv", venv
+ system venv/"bin/pip", "install", "--upgrade", "pip", "setuptools", "wheel"
+
+ # Install Cantera from PyPI using pre-built wheel (complex package, doesn't need custom flags)
+ # Cantera has CMake compatibility issues when building from source with newer CMake versions
+ system venv/"bin/pip", "install", "cantera==3.1.0"
+
+ # Install MFC Python package and dependencies into venv
+ # Use editable install (-e) to avoid RECORD file issues when venv is symlinked at runtime
+ # Dependencies will use pre-built wheels from PyPI
+ # Keep toolchain in buildpath for now - mfc.sh needs it there
+ system venv/"bin/pip", "install", "-e", buildpath/"toolchain"
+
+ # Create symlink so mfc.sh uses our pre-installed venv
+ mkdir_p "build"
+ ln_sf venv, "build/venv"
+
+ # Now build MFC with pre-configured venv
+ # Set VIRTUAL_ENV so mfc.sh uses existing venv instead of creating new one
+ ENV["VIRTUAL_ENV"] = venv
+
+ # Build MFC using pre-configured venv
+ # Must run from buildpath (MFC root directory) where toolchain/ exists
+ Dir.chdir(buildpath) do
+ system "./mfc.sh", "build", "-t", "pre_process", "simulation", "post_process", "-j", ENV.make_jobs.to_s
+ end
+
+ # After build completes, install Python toolchain to prefix
+ prefix.install "toolchain"
+
+ # Install only executable files from hashed build dirs
+ Dir.glob("build/install/*/bin/*").each do |p|
+ next if !File.file?(p) || !File.executable?(p)
+
+ bin.install p
+ (bin/File.basename(p)).chmod 0755
+ end
+
+ # Install main mfc.sh script
+ libexec.install "mfc.sh"
+
+ # Install examples
+ prefix.install "examples"
+
+ # Create smart wrapper script in libexec; bin wrapper will be generated by Homebrew
+ (libexec/"mfc").write <<~EOS
+ #!/usr/bin/env bash
+ set -euo pipefail
+
+ # Unset VIRTUAL_ENV to ensure mfc.sh uses our configured venv
+ unset VIRTUAL_ENV || true
+
+ # Save original working directory
+ ORIG_DIR="$(pwd)"
+
+ # Process arguments and convert relative paths to absolute paths
+ declare -a ARGS=()
+ for arg in "$@"; do
+ # If argument looks like a file path and exists as relative path
+ if [[ "$arg" =~ \\.(py|txt|json|yaml|yml)$ ]] && [ -e "${ORIG_DIR}/${arg}" ]; then
+ ARGS+=("${ORIG_DIR}/${arg}")
+ else
+ ARGS+=("$arg")
+ fi
+ done
+
+ SUBCMD="${ARGS[0]-}"
+
+ # Friendly help and guardrails
+ if [[ ${#ARGS[@]} -eq 0 ]] || [[ "${SUBCMD}" == "--help" ]] || [[ "${SUBCMD}" == "-h" ]]; then
+ cat <<'HHELP'
+ MFC (Homebrew) #{version}
+
+ Usage:
+ mfc run [options]
+
+ Examples:
+ mfc run case.py -j 1
+
+ Notes:
+ - This Homebrew wrapper uses prebuilt binaries and a preinstalled venv.
+ - Developer commands (build, clean, test, etc.) are not available here.
+ Clone the MFC repo for the full developer workflow.
+ HHELP
+ exit 0
+ fi
+
+ if [[ "${SUBCMD}" != "run" ]]; then
+ echo "mfc (Homebrew): only 'run' is supported in the Homebrew package."
+ echo "Use 'mfc run ' or clone the repository for developer commands."
+ exit 2
+ fi
+
+ # Create a temporary working directory (Cellar is read-only)
+ TMPDIR="$(mktemp -d)"
+ trap 'rm -rf "${TMPDIR}"' EXIT
+ cd "${TMPDIR}"
+
+ # Copy only mfc.sh (small, fast)
+ cp "#{libexec}/mfc.sh" .
+
+ # Create a minimal CMakeLists.txt to prevent the cat error
+ # mfc.sh reads this to get version info
+ cat > CMakeLists.txt << 'CMAKE_EOF'
+ cmake_minimum_required(VERSION 3.18)
+ project(MFC VERSION #{version})
+ CMAKE_EOF
+
+ # Symlink toolchain (read-only is fine, no copy needed)
+ ln -s "#{prefix}/toolchain" toolchain
+
+ # Symlink examples (read-only is fine)
+ ln -s "#{prefix}/examples" examples
+
+ # Create build directory structure
+ mkdir -p build
+
+ # Symlink the persistent venv (no copy)
+ ln -s "#{libexec}/venv" build/venv
+
+ # Copy only pyproject.toml (tiny file, prevents reinstall checks)
+ cp "#{prefix}/toolchain/pyproject.toml" build/pyproject.toml
+
+ # Create a sitecustomize.py file that patches MFC paths before any imports
+ # This runs automatically at Python startup and ensures paths are correct
+ cat > sitecustomize.py << 'SITECUSTOMIZE_EOF'
+ import sys
+ import os
+
+ # Add toolchain to path
+ sys.path.insert(0, "#{prefix}/toolchain")
+
+ # Patch MFC paths before mfc.common is imported anywhere
+ _mfc_temp_root = os.getcwd()
+
+ def _patch_mfc_common():
+ """Patches mfc.common module to use temp directory for writable files."""
+ import mfc.common
+ mfc.common.MFC_ROOT_DIR = _mfc_temp_root
+ mfc.common.MFC_BUILD_DIR = os.path.join(_mfc_temp_root, "build")
+ mfc.common.MFC_LOCK_FILEPATH = os.path.join(mfc.common.MFC_BUILD_DIR, "lock.yaml")
+ # Keep toolchain and examples pointing to Homebrew installation
+ mfc.common.MFC_TOOLCHAIN_DIR = "#{prefix}/toolchain"
+ mfc.common.MFC_EXAMPLE_DIRPATH = "#{prefix}/examples"
+
+ def _patch_mfc_build():
+ """Patches MFCTarget to use pre-installed binaries."""
+ from mfc.build import MFCTarget
+
+ # Override get_install_binpath to use pre-installed binaries
+ _original_get_install_binpath = MFCTarget.get_install_binpath
+ def _homebrew_get_install_binpath(self, case):
+ return "#{bin}/" + self.name
+ MFCTarget.get_install_binpath = _homebrew_get_install_binpath
+
+ # Override is_buildable to skip building main targets
+ _original_is_buildable = MFCTarget.is_buildable
+ def _homebrew_is_buildable(self):
+ if self.name in ["pre_process", "simulation", "post_process", "syscheck"]:
+ return False
+ return _original_is_buildable(self)
+ MFCTarget.is_buildable = _homebrew_is_buildable
+
+ # Apply patches immediately
+ _patch_mfc_common()
+ _patch_mfc_build()
+ SITECUSTOMIZE_EOF
+
+ # Set PYTHONPATH to include current directory so sitecustomize.py is found
+ export PYTHONPATH="${TMPDIR}:#{prefix}/toolchain:${PYTHONPATH:-}"
+
+ # Always run with --no-build in Homebrew package
+ exec ./mfc.sh "${ARGS[@]}" --no-build
+ EOS
+ (libexec/"mfc").chmod 0755
+
+ # Create a thin exec wrapper in bin that calls the libexec script (audit-friendly)
+ bin.write_exec_script libexec/"mfc"
+ end
+
+ def post_install
+ # Fix executable permissions for libexec wrapper
+ (libexec/"mfc").chmod 0755
+ end
+
+ def caveats
+ <<~EOS
+ MFC has been installed successfully!
+
+ To run a case:
+ mfc run
+
+ Pre-built binaries are also available directly:
+ pre_process, simulation, post_process
+
+ Examples are available in:
+ #{prefix}/examples
+
+ Example:
+ cp #{prefix}/examples/1D_sodshocktube/case.py .
+ mfc run case.py
+
+ Note: Cantera 3.1.0 is pre-installed in the MFC virtual environment.
+ EOS
+ end
+
+ test do
+ # Test that all binaries exist and are executable
+ %w[pre_process simulation post_process].each do |prog|
+ assert_path_exists bin/prog
+ assert_predicate bin/prog, :executable?
+ end
+
+ # Test that toolchain is installed
+ assert_path_exists prefix/"toolchain"
+
+ # Test that venv exists and has required packages
+ assert_path_exists libexec/"venv"
+ assert_predicate (libexec/"venv/bin/python"), :executable?
+
+ # Test that examples exist
+ assert_path_exists prefix/"examples"
+
+ # Test that mfc wrapper works
+ system bin/"mfc", "--help"
+
+ # Test running a simple 1D Sod shock tube case from a separate directory
+ # This ensures the wrapper script correctly handles relative paths
+ testpath_case = testpath/"test_run"
+ testpath_case.mkpath
+
+ # Copy case.py from examples to an independent test directory
+ cp prefix/"examples/1D_sodshocktube/case.py", testpath_case/"case.py"
+
+ # Run the case from the test directory (this will execute pre_process and simulation)
+ # Limit to 1 processor and reduce runtime for testing
+ cd testpath_case do
+ system bin/"mfc", "run", "case.py", "-j", "1"
+ end
+
+ # Verify output files were created in the test directory
+ assert_path_exists testpath_case/"silo_hdf5"
+ assert_predicate testpath_case/"silo_hdf5", :directory?
+ end
+end