diff --git a/.github/workflows/deploy-tap.yml b/.github/workflows/deploy-tap.yml new file mode 100644 index 0000000000..8c34e10c19 --- /dev/null +++ b/.github/workflows/deploy-tap.yml @@ -0,0 +1,71 @@ +name: Deploy to Homebrew Tap + +on: + push: + branches: + - master + paths: + - 'packaging/homebrew/mfc.rb' + workflow_dispatch: + +permissions: + contents: write + +jobs: + deploy-tap: + name: Deploy Formula to Tap + runs-on: ubuntu-latest + steps: + - name: Checkout MFC repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Checkout tap repository + uses: actions/checkout@v4 + with: + repository: MFlowCode/homebrew-mfc + token: ${{ secrets.GITHUB_TOKEN }} + path: tap-repo + + - name: Setup Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Copy formula to tap + run: | + mkdir -p tap-repo/Formula + cp packaging/homebrew/mfc.rb tap-repo/Formula/mfc.rb + + - name: Check for changes + id: check-changes + run: | + cd tap-repo + if git ls-files --error-unmatch Formula/mfc.rb >/dev/null 2>&1; then + # File exists in repo, check if it changed + if git diff --quiet HEAD -- Formula/mfc.rb; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi + else + # File doesn't exist in repo, it's a new file + echo "changed=true" >> $GITHUB_OUTPUT + fi + + - name: Commit and push to tap + if: steps.check-changes.outputs.changed == 'true' + run: | + cd tap-repo + git add Formula/mfc.rb + git commit -m "Update mfc formula from MFC repository + + Auto-deployed from MFlowCode/MFC@${{ github.sha }}" + git push + + - name: No changes detected + if: steps.check-changes.outputs.changed == 'false' + run: | + echo "No changes detected in formula. Skipping deployment." + diff --git a/.github/workflows/homebrew.yml b/.github/workflows/homebrew.yml new file mode 100644 index 0000000000..c20db9dab3 --- /dev/null +++ b/.github/workflows/homebrew.yml @@ -0,0 +1,167 @@ +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: + test-formula: + name: Test Homebrew Formula + 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: Validate formula syntax + run: | + echo "Checking formula syntax..." + brew style packaging/homebrew/mfc.rb + + - 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)/Cellar/mfc/*/libexec/venv + test -f $(brew --prefix)/Cellar/mfc/*/libexec/venv/bin/python + test -f $(brew --prefix)/Cellar/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/packaging/homebrew/HOMEBREW.md b/packaging/homebrew/HOMEBREW.md new file mode 100644 index 0000000000..07140322fa --- /dev/null +++ b/packaging/homebrew/HOMEBREW.md @@ -0,0 +1,482 @@ +# MFC Homebrew Formula + +This document describes the Homebrew formula for installing MFC on macOS systems. + +## Overview + +The Homebrew formula enables one-command installation of MFC on macOS (both Intel and Apple Silicon). The formula handles all dependencies, builds the three main MFC binaries, and installs them in a standard Homebrew location. + +## Quick Start Guide + +### Prerequisites + +- macOS 11 (Big Sur) or later +- [Homebrew](https://brew.sh/) installed on your system +- Internet connection for downloading dependencies + +### Installation + +The installation method depends on how the formula is distributed: + +#### Option 1: Official Homebrew Repository (Future) + +Once the formula is accepted into `homebrew-core`, install with: + +```bash +brew install mfc +``` + +#### Option 2: Third-Party Tap (Recommended) + +Install from the official MFC Homebrew tap: + +```bash +# Add the tap +brew tap MFlowCode/mfc + +# Install MFC +brew install MFlowCode/mfc/mfc +``` + +The tap is automatically kept up-to-date with the latest formula changes. + +#### Option 3: Install from Local Formula File + +If you have the formula file locally (e.g., from cloning the MFC repository): + +```bash +# Install directly from the formula file +brew install --build-from-source /path/to/MFC/packaging/homebrew/mfc.rb +``` + +Or create a local tap: + +```bash +# Create a local tap +brew tap-new MFlowCode/local + +# Copy the formula to the tap +cp packaging/homebrew/mfc.rb $(brew --repository)/Library/Taps/MFlowCode/homebrew-local/Formula/mfc.rb + +# Install from the local tap +brew install MFlowCode/local/mfc +``` + +#### Option 4: Install from GitHub Repository + +If the formula is in a GitHub repository: + +```bash +brew install MFlowCode/MFC/packaging/homebrew/mfc.rb +``` + +### Verification + +After installation, verify that MFC is installed correctly: + +```bash +# Check that the mfc command is available +mfc --help + +# Verify binaries are installed +which pre_process simulation post_process + +# Run Homebrew's built-in tests +brew test mfc +``` + +### Basic Usage + +Once installed, you can start using MFC immediately: + +```bash +# Get help +mfc --help + +# Copy an example case to your working directory +cp $(brew --prefix mfc)/examples/1D_sodshocktube/case.py . + +# Run a simulation case +mfc run case.py + +# Or run directly from the installed examples +mfc run $(brew --prefix mfc)/examples/1D_sodshocktube/case.py +``` + +### Uninstallation + +To remove MFC from your system: + +```bash +# Uninstall MFC +brew uninstall mfc + +# If installed from a tap, you may also want to remove the tap +brew untap MFlowCode/mfc # Replace with your tap name if different +``` + +**Note**: Uninstalling MFC will remove: +- All MFC binaries (`mfc`, `pre_process`, `simulation`, `post_process`) +- The Python virtual environment and toolchain +- Example cases +- All associated files + +The uninstallation is clean and complete - Homebrew will remove all files that were installed by the formula. + +## What Gets Installed + +When users run `brew install mfc`, they get: + +### Binaries +- `pre_process` - Preprocessing binary for setting up initial conditions +- `simulation` - Main simulation binary for computational fluid dynamics +- `post_process` - Post-processing binary for analyzing results +- `mfc` - Wrapper script that provides the full MFC interface + +### Additional Components +- Python toolchain in `/usr/local/Cellar/mfc/VERSION/toolchain/` or `/opt/homebrew/Cellar/mfc/VERSION/toolchain/` +- Python virtual environment in `/usr/local/Cellar/mfc/VERSION/libexec/venv/` or `/opt/homebrew/Cellar/mfc/VERSION/libexec/venv/` + - Pre-installed with Cantera 3.1.0 and MFC toolchain packages +- Example cases in `/usr/local/Cellar/mfc/VERSION/examples/` or `/opt/homebrew/Cellar/mfc/VERSION/examples/` +- Main `mfc.sh` script in `libexec/` +- Documentation references and usage information + +## Formula Structure + +### Dependencies + +Build-time dependencies (only needed during installation): +- cmake - Build system generator +- gcc - GNU Compiler Collection (provides gfortran) + +Runtime dependencies (needed to run MFC): +- boost - C++ libraries +- fftw - Fast Fourier Transform library +- hdf5 - Hierarchical Data Format 5 for data storage +- open-mpi - Message Passing Interface for parallel computing +- openblas - Optimized BLAS library +- python@3.12 - Python 3.12 (used for build scripts and runtime virtual environment) + +Python package dependencies (installed in virtual environment): +- cantera==3.1.0 - Chemical kinetics library (required for MFC build and runtime) +- MFC toolchain package - Python utilities for MFC operations (installed in editable mode) + +### Virtual Environment Setup + +Before building, the formula creates a Python virtual environment to isolate MFC's Python dependencies: + +1. Creates a new virtual environment in `libexec/venv` using Python 3.12 +2. Upgrades pip, setuptools, and wheel to latest versions +3. Installs Cantera 3.1.0 from PyPI (required dependency for MFC) +4. Installs the MFC toolchain package in editable mode (`-e`) to avoid RECORD file issues +5. Creates a symlink from `build/venv` to the venv so `mfc.sh` can find it during build + +This virtual environment persists after installation and is used by the wrapper script at runtime. + +### Build Process + +The formula executes the following steps during installation: + +1. **Virtual Environment Setup** (see section above) +2. Sets `VIRTUAL_ENV` environment variable so `mfc.sh` uses the pre-configured venv +3. Runs `./mfc.sh build -t pre_process simulation post_process -j ` to compile all three binaries +4. Installs binaries from `build/install/*/bin/*` to Homebrew's bin directory +5. Installs `mfc.sh` to `libexec/` for script execution +6. Installs Python toolchain directory to `prefix/toolchain/` (required for mfc.sh functionality) +7. Installs examples directory to `prefix/examples/` +8. Creates and installs the `mfc` wrapper script that handles runtime environment setup + +### Environment Configuration + +The formula relies on Homebrew's automatic environment setup: +- Compiler flags are set via the `gcc` dependency (Homebrew's superenv) +- Library paths are automatically configured +- The wrapper script sets BOOST_INCLUDE at runtime for user commands + +### Wrapper Script + +The installed `mfc` wrapper provides the complete MFC interface. Due to Homebrew's read-only Cellar structure, the wrapper implements a sophisticated workaround: + +**Key Features:** +1. **Temporary Working Directory**: Creates a temporary directory since the Cellar is read-only and `mfc.sh` may need to write build artifacts +2. **Environment Setup**: Copies `mfc.sh`, toolchain, examples, and the virtual environment to the temp directory +3. **Toolchain Patching**: Dynamically patches the toolchain Python code to: + - Use pre-installed binaries from Homebrew's bin directory instead of building new ones + - Skip building main targets (`pre_process`, `simulation`, `post_process`, `syscheck`) since they're already installed +4. **Build Optimization**: Automatically adds `--no-build` flag to `mfc run` commands to skip unnecessary compilation +5. **Cleanup**: Automatically removes the temporary directory when the command completes + +Users can run any MFC command through this wrapper: + +```bash +mfc build +mfc run examples/case.py +mfc test +mfc clean +``` + +The wrapper ensures all MFC functionality works correctly while respecting Homebrew's installation constraints. + +## Installation Methods + +### Standard Installation + +Users install MFC with: +```bash +brew install mfc +``` + +This fetches the source tarball from GitHub releases, verifies the checksum, and builds from source. + +### Development Installation + +For the latest development version: +```bash +brew install --HEAD mfc +``` + +This clones from the master branch instead of using a release tarball. + +## Testing + +The formula includes automated tests that verify: +- All three binary files exist after installation and are executable +- The Python toolchain directory is installed correctly +- The virtual environment exists and contains an executable Python interpreter +- The examples directory is installed +- The mfc wrapper script is functional and responds to `--help` + +These tests run automatically during `brew install` and can be run manually with `brew test mfc`. + +## Post-Installation + +After installation completes, Homebrew displays usage information including: +- Locations of installed binaries +- Path to example cases +- Example command to run a simulation +- Link to full documentation + +## Usage Examples + +Once installed, users can immediately start using MFC. Here are common usage patterns: + +### Running Example Cases + +```bash +# List available examples +ls $(brew --prefix mfc)/examples + +# Copy an example case to your working directory +cp $(brew --prefix mfc)/examples/1D_sodshocktube/case.py . + +# Run a test case (recommended: copy to working directory first) +mfc run case.py + +# Or run directly from the installed examples directory +mfc run $(brew --prefix mfc)/examples/1D_sodshocktube/case.py + +# Run with specific number of processors +mfc run case.py -j 4 +``` + +### Using Individual Binaries + +You can also use the binaries directly: + +```bash +# Run preprocessing step +pre_process -i input.dat + +# Run simulation +simulation -i input.dat + +# Run post-processing +post_process -i input.dat + +# Check binary versions +pre_process --version +simulation --version +post_process --version +``` + +### Common MFC Commands + +```bash +# Get help +mfc --help + +# Build MFC (if you have source code) +mfc build + +# Run tests +mfc test + +# Clean build artifacts +mfc clean + +# Check system requirements +mfc syscheck +``` + +### Working with Your Own Cases + +```bash +# Create a new case directory +mkdir my_case +cd my_case + +# Create your case.py file (see MFC documentation for format) +# Then run it +mfc run case.py + +# Run with custom parameters +mfc run case.py --param value +``` + +### Finding Installed Files + +```bash +# Get the installation prefix +brew --prefix mfc + +# Find examples +ls $(brew --prefix mfc)/examples + +# Find toolchain +ls $(brew --prefix mfc)/toolchain + +# Check installed version +brew info mfc +``` + +### Troubleshooting + +```bash +# Verify installation +brew list mfc + +# Check for issues +brew doctor + +# Reinstall if needed +brew reinstall mfc + +# View installation logs +brew install --verbose mfc + +# Uninstall MFC +brew uninstall mfc + +# Remove tap (if installed from a tap) +brew untap MFlowCode/mfc +``` + +**Note**: The `brew --prefix mfc` command returns the "opt" symlink location (e.g., `/usr/local/opt/mfc` or `/opt/homebrew/opt/mfc`), not the actual versioned Cellar path. The "opt" directory points to the currently active version in the Cellar, making examples work on both Intel and Apple Silicon systems. If you need the actual versioned installation path, look in the Cellar directory (e.g., `/usr/local/Cellar/mfc/VERSION`). + +## Distribution + +The formula can be distributed in two ways: + +### Official Homebrew Repository +Submit a pull request to homebrew-core for inclusion in the main Homebrew repository. This requires: +- Stable release with version tag +- Verified tarball checksum +- Formula code review +- Automated testing passes + +### Third-Party Tap + +MFC maintains an official Homebrew tap at `MFlowCode/homebrew-mfc` for easy installation: + +```bash +# Add the tap +brew tap MFlowCode/mfc + +# Install MFC +brew install MFlowCode/mfc/mfc +``` + +The tap is automatically updated whenever the formula changes in the main MFC repository. This allows distribution before official Homebrew acceptance and provides a convenient installation method for users. + +**Tap Repository**: https://github.com/MFlowCode/homebrew-mfc + +The tap repository contains: +- `Formula/mfc.rb` - The Homebrew formula file (automatically synced from the main repository) +- Automatic deployment via GitHub Actions when the formula is updated + +## Platform Support + +The formula supports: +- macOS 11 (Big Sur) and later +- Intel x86_64 processors +- Apple Silicon (ARM64) processors + +Homebrew automatically selects the appropriate compiler flags and optimization settings for each architecture. + +## Updates and Versioning + +When a new MFC version is released, the formula must be updated. To update to a new version: + +1. Change the version number in the URL +2. Download the new tarball +3. Calculate new SHA256 checksum +4. Update the sha256 line in the formula +5. Test the installation +6. Submit updated formula + +## Technical Details + +### Source URL +The formula downloads from GitHub releases using the version tag specified in the formula (e.g., v5.1.0): +``` +https://github.com/MFlowCode/MFC/archive/refs/tags/vVERSION.tar.gz +``` + +### Checksum Verification +The formula includes a SHA256 checksum that must match the downloaded tarball. This ensures: +- The downloaded source matches the expected file exactly +- No corruption occurred during download +- Authenticity of the release + +Maintainers must update this checksum when releasing new versions. + +### Build Parallelization +The formula uses all available CPU cores for building (`ENV.make_jobs`) to minimize compilation time. + +### Installation Prefix +Files install to the standard Homebrew prefix: +- Binaries: `/usr/local/bin/` (Intel) or `/opt/homebrew/bin/` (Apple Silicon) +- Main script: `/usr/local/Cellar/mfc/VERSION/libexec/mfc.sh` or `/opt/homebrew/Cellar/mfc/VERSION/libexec/mfc.sh` +- Toolchain: `/usr/local/Cellar/mfc/VERSION/toolchain/` or `/opt/homebrew/Cellar/mfc/VERSION/toolchain/` +- Virtual environment: `/usr/local/Cellar/mfc/VERSION/libexec/venv/` or `/opt/homebrew/Cellar/mfc/VERSION/libexec/venv/` +- Examples: `/usr/local/Cellar/mfc/VERSION/examples/` or `/opt/homebrew/Cellar/mfc/VERSION/examples/` + +## Advantages Over Manual Installation + +Users benefit from Homebrew installation: + +1. Automatic dependency management - Homebrew installs all required libraries +2. Pre-compiled binaries - On some systems, bottles (binary packages) may be available +3. Easy updates - `brew upgrade mfc` gets the latest version +4. Clean uninstallation - `brew uninstall mfc` removes everything +5. Standard paths - Binaries are automatically in PATH +6. Version management - Multiple versions can coexist if needed + +## Maintenance + +The formula requires minimal maintenance once accepted: +- Update version and checksum when new releases are published +- Adjust dependencies if MFC requirements change +- Update minimum macOS version if newer features are needed +- Monitor for deprecated Homebrew APIs and update accordingly + +## Validation + +The formula has been tested on: +- macOS 14 (Sonoma) with Apple Silicon +- macOS 13 (Ventura) with Intel +- Fresh installations and upgrades +- Both stable release versions and HEAD (development) versions + +All tests pass and the installation completes successfully on all tested platforms. + diff --git a/packaging/homebrew/mfc.rb b/packaging/homebrew/mfc.rb new file mode 100644 index 0000000000..27d0b4b8e0 --- /dev/null +++ b/packaging/homebrew/mfc.rb @@ -0,0 +1,187 @@ +# typed: strict +# frozen_string_literal: true + +# Homebrew formula for MFC (Multiphase Flow Code) +# This formula is automatically deployed to the MFlowCode/homebrew-mfc tap +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" + + # Preserve venv RECORD files (needed for pip to manage packages) + skip_clean "libexec/venv" + + def install + # Create Python virtual environment (remove existing one first for clean reinstalls) + venv = libexec/"venv" + rm_rf 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 (required dependency for MFC build) + system venv/"bin/pip", "install", "cantera==3.1.0" + + # Install MFC Python package and dependencies into venv + # Keep toolchain in buildpath for now - mfc.sh needs it there + # Use editable install (-e) to avoid RECORD file issues when venv is copied + # Note: Homebrew may warn about dylib fixup failures for some Python packages (e.g., orjson) + # This is non-fatal since the venv is copied (not linked) at runtime + 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 binaries - they're in hashed subdirectories like build/install//bin/* + Dir.glob("build/install/*/bin/*").each do |binary_path| + bin.install binary_path + end + + # Install main mfc.sh script + libexec.install "mfc.sh" + + # Install examples + prefix.install "examples" + + # Create smart wrapper script that: + # 1. Works around read-only Cellar issue by copying venv to tmpdir + # 2. Sets up toolchain symlink so mfc.sh can find toolchain/util.sh + # 3. Ensures mfc.sh doesn't reinstall packages by copying pyproject.toml + + # Create wrapper script in buildpath first, then install it + wrapper_script = buildpath/"mfc_wrapper" + wrapper_script.write <<~EOS + #!/bin/bash + set -euo pipefail + + # Unset VIRTUAL_ENV to ensure mfc.sh uses the copied venv, not the Cellar one + unset VIRTUAL_ENV || true + + # Create a temporary working directory (Cellar is read-only) + TMPDIR="$(mktemp -d)" + trap 'rm -rf "${TMPDIR}"' EXIT + + # Copy mfc.sh to temp dir (it may try to write build artifacts) + cp "#{libexec}/mfc.sh" "${TMPDIR}/" + cd "${TMPDIR}" + + # Copy toolchain directory (not symlink) so Python paths resolve correctly + # This prevents paths from resolving back to read-only Cellar + cp -R "#{prefix}/toolchain" "toolchain" + + # Patch toolchain to use Homebrew-installed binaries + # Replace get_install_binpath to return Homebrew bin directory + cat >> "toolchain/mfc/build.py" << 'PATCH_EOF' + + # Homebrew patch: 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 and syscheck + _original_is_buildable = MFCTarget.is_buildable + def _homebrew_is_buildable(self): + if self.name in ["pre_process", "simulation", "post_process", "syscheck"]: + return False # Skip building - use pre-installed binaries + return _original_is_buildable(self) + MFCTarget.is_buildable = _homebrew_is_buildable + PATCH_EOF + + # Copy examples directory (required by mfc.sh Python code) + cp -R "#{prefix}/examples" "examples" + + # Create build directory and copy venv (not symlink - needs to be writable) + # Use cp -R for a full recursive copy + mkdir -p "build" + cp -R "#{venv}" "build/venv" + + # Copy pyproject.toml to build/ so mfc.sh thinks dependencies are already installed + cp "#{prefix}/toolchain/pyproject.toml" "build/pyproject.toml" + + # For 'mfc run', add --no-build flag to skip compilation + if [ "${1-}" = "run" ]; then + exec ./mfc.sh "$@" --no-build + else + exec ./mfc.sh "$@" + fi + EOS + + # Make wrapper executable and install it + wrapper_script.chmod 0755 + bin.install wrapper_script => "mfc" + end + + def post_install + # Fix executable permissions (Homebrew sometimes overrides them) + (bin/"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" + end +end