|
| 1 | +name: Homebrew Formula Test |
| 2 | + |
| 3 | +on: |
| 4 | + push: |
| 5 | + branches: |
| 6 | + - master |
| 7 | + - homebrew-formula |
| 8 | + paths: |
| 9 | + - 'packaging/homebrew/**' |
| 10 | + - '.github/workflows/homebrew.yml' |
| 11 | + pull_request: |
| 12 | + branches: |
| 13 | + - master |
| 14 | + paths: |
| 15 | + - 'packaging/homebrew/**' |
| 16 | + - '.github/workflows/homebrew.yml' |
| 17 | + workflow_dispatch: |
| 18 | + |
| 19 | +jobs: |
| 20 | + # Fast smoke tests that run before expensive operations |
| 21 | + smoke-test: |
| 22 | + name: Quick Formula Validation |
| 23 | + runs-on: ubuntu-latest # Use Linux for speed (Homebrew works on Linux too) |
| 24 | + |
| 25 | + steps: |
| 26 | + - name: Checkout repository |
| 27 | + uses: actions/checkout@v4 |
| 28 | + |
| 29 | + - name: Set up Homebrew |
| 30 | + uses: Homebrew/actions/setup-homebrew@master |
| 31 | + |
| 32 | + - name: Validate formula syntax with brew style |
| 33 | + run: | |
| 34 | + echo "Checking formula syntax..." |
| 35 | + brew style packaging/homebrew/mfc.rb |
| 36 | + |
| 37 | + - name: Run brew audit (without installation) |
| 38 | + run: | |
| 39 | + echo "Configuring git for brew tap-new..." |
| 40 | + git config --global user.email "github-actions[bot]@users.noreply.github.com" |
| 41 | + git config --global user.name "github-actions[bot]" |
| 42 | + |
| 43 | + echo "Creating temporary local tap..." |
| 44 | + brew tap-new mflowcode/test |
| 45 | + cp packaging/homebrew/mfc.rb $(brew --repository)/Library/Taps/mflowcode/homebrew-test/Formula/mfc.rb |
| 46 | + |
| 47 | + echo "Running brew audit (online checks)..." |
| 48 | + brew audit --online --skip-style mflowcode/test/mfc || true |
| 49 | + |
| 50 | + echo "Cleaning up tap..." |
| 51 | + brew untap mflowcode/test |
| 52 | + |
| 53 | + - name: Validate Ruby syntax |
| 54 | + run: | |
| 55 | + echo "Checking Ruby syntax..." |
| 56 | + ruby -c packaging/homebrew/mfc.rb |
| 57 | + |
| 58 | + - name: Check for common formula issues |
| 59 | + run: | |
| 60 | + echo "Checking for common issues..." |
| 61 | + |
| 62 | + # Check that required fields are present |
| 63 | + grep -q 'desc "' packaging/homebrew/mfc.rb || (echo "❌ Missing desc"; exit 1) |
| 64 | + grep -q 'homepage "' packaging/homebrew/mfc.rb || (echo "❌ Missing homepage"; exit 1) |
| 65 | + grep -q 'url "' packaging/homebrew/mfc.rb || (echo "❌ Missing url"; exit 1) |
| 66 | + grep -q 'sha256 "' packaging/homebrew/mfc.rb || (echo "❌ Missing sha256"; exit 1) |
| 67 | + grep -q 'license "' packaging/homebrew/mfc.rb || (echo "❌ Missing license"; exit 1) |
| 68 | + |
| 69 | + # Check that install method exists |
| 70 | + grep -q 'def install' packaging/homebrew/mfc.rb || (echo "❌ Missing install method"; exit 1) |
| 71 | + |
| 72 | + # Check that test block exists |
| 73 | + grep -q 'test do' packaging/homebrew/mfc.rb || (echo "❌ Missing test block"; exit 1) |
| 74 | + |
| 75 | + echo "✅ All required formula components present" |
| 76 | + |
| 77 | + - name: Verify URL is reachable |
| 78 | + run: | |
| 79 | + echo "Checking that source URL is reachable..." |
| 80 | + URL=$(grep -E 'url "https://[^"]+' packaging/homebrew/mfc.rb | head -1 | sed 's/.*url "\([^"]*\)".*/\1/') |
| 81 | + |
| 82 | + if [ -z "$URL" ]; then |
| 83 | + echo "❌ Could not extract URL from formula" |
| 84 | + exit 1 |
| 85 | + fi |
| 86 | + |
| 87 | + echo "URL: $URL" |
| 88 | + HTTP_CODE=$(curl -sI -w "%{http_code}" -o /dev/null "$URL") |
| 89 | + |
| 90 | + if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ]; then |
| 91 | + echo "✅ URL is reachable (HTTP $HTTP_CODE)" |
| 92 | + else |
| 93 | + echo "⚠️ URL returned HTTP $HTTP_CODE (may indicate an issue)" |
| 94 | + # Don't fail here - could be a temporary issue |
| 95 | + fi |
| 96 | + |
| 97 | + - name: Verify SHA256 checksum |
| 98 | + run: | |
| 99 | + echo "Verifying SHA256 checksum matches URL..." |
| 100 | + URL=$(grep -E 'url "https://[^"]+' packaging/homebrew/mfc.rb | head -1 | sed 's/.*url "\([^"]*\)".*/\1/') |
| 101 | + EXPECTED_SHA=$(grep 'sha256 "' packaging/homebrew/mfc.rb | head -1 | sed 's/.*sha256 "\([^"]*\)".*/\1/') |
| 102 | + |
| 103 | + if [ -z "$URL" ] || [ -z "$EXPECTED_SHA" ]; then |
| 104 | + echo "❌ Could not extract URL or SHA256 from formula" |
| 105 | + exit 1 |
| 106 | + fi |
| 107 | + |
| 108 | + echo "Downloading tarball to compute checksum..." |
| 109 | + ACTUAL_SHA=$(curl -sL "$URL" | shasum -a 256 | awk '{print $1}') |
| 110 | + |
| 111 | + echo "Expected SHA256: $EXPECTED_SHA" |
| 112 | + echo "Actual SHA256: $ACTUAL_SHA" |
| 113 | + |
| 114 | + if [ "$EXPECTED_SHA" = "$ACTUAL_SHA" ]; then |
| 115 | + echo "✅ SHA256 checksum matches!" |
| 116 | + else |
| 117 | + echo "❌ SHA256 mismatch!" |
| 118 | + exit 1 |
| 119 | + fi |
| 120 | +
|
| 121 | + # Full installation test (only runs if smoke tests pass) |
| 122 | + test-formula: |
| 123 | + name: Full Installation Test |
| 124 | + needs: smoke-test # Only run after smoke tests pass |
| 125 | + runs-on: macos-latest |
| 126 | + |
| 127 | + steps: |
| 128 | + - name: Checkout repository |
| 129 | + uses: actions/checkout@v4 |
| 130 | + |
| 131 | + - name: Set up Homebrew |
| 132 | + run: | |
| 133 | + echo "Homebrew version:" |
| 134 | + brew --version |
| 135 | + echo "Updating Homebrew..." |
| 136 | + brew update |
| 137 | + |
| 138 | + - name: Install formula dependencies |
| 139 | + run: | |
| 140 | + echo "Installing MFC dependencies..." |
| 141 | + brew install cmake gcc [email protected] boost fftw hdf5 open-mpi openblas |
| 142 | + |
| 143 | + - name: Install MFC from formula |
| 144 | + run: | |
| 145 | + echo "Creating temporary local tap..." |
| 146 | + brew tap-new mflowcode/test |
| 147 | + |
| 148 | + echo "Copying formula to tap..." |
| 149 | + cp packaging/homebrew/mfc.rb $(brew --repository)/Library/Taps/mflowcode/homebrew-test/Formula/mfc.rb |
| 150 | + |
| 151 | + echo "Installing MFC from local tap..." |
| 152 | + # Note: brew may exit with code 1 due to dylib fixup warnings on some Python packages (non-fatal) |
| 153 | + # We verify installation using brew commands rather than parsing log output |
| 154 | + set +e # Don't fail immediately on error |
| 155 | + brew install --build-from-source --verbose mflowcode/test/mfc 2>&1 | tee /tmp/brew-install.log |
| 156 | + brew_exit_code=$? |
| 157 | + set -e |
| 158 | + |
| 159 | + # Verify installation using brew list (more robust than log parsing) |
| 160 | + if brew list mflowcode/test/mfc &>/dev/null; then |
| 161 | + echo "✅ MFC installed successfully (ignoring dylib relocation warnings)" |
| 162 | + # Optionally verify with brew info |
| 163 | + brew info mflowcode/test/mfc |
| 164 | + exit 0 |
| 165 | + else |
| 166 | + echo "❌ MFC installation failed" |
| 167 | + exit $brew_exit_code |
| 168 | + fi |
| 169 | + |
| 170 | + - name: Display error logs on failure |
| 171 | + if: failure() |
| 172 | + run: | |
| 173 | + echo "=== Displaying last 200 lines of brew install log ===" |
| 174 | + if [ -f /tmp/brew-install.log ]; then |
| 175 | + tail -200 /tmp/brew-install.log |
| 176 | + fi |
| 177 | + |
| 178 | + echo -e "\n=== Displaying Homebrew log files ===" |
| 179 | + if [ -d ~/Library/Logs/Homebrew/mfc/ ]; then |
| 180 | + for logfile in ~/Library/Logs/Homebrew/mfc/*; do |
| 181 | + if [ -f "$logfile" ]; then |
| 182 | + echo -e "\n\n====== $logfile ======" |
| 183 | + cat "$logfile" |
| 184 | + fi |
| 185 | + done |
| 186 | + fi |
| 187 | + |
| 188 | + echo -e "\n=== Searching for Cantera config.log ===" |
| 189 | + cantera_config_log=$(find /private/tmp -name "config.log" -path "*/mfc--cantera*" 2>/dev/null | head -1) |
| 190 | + if [ -n "$cantera_config_log" ] && [ -f "$cantera_config_log" ]; then |
| 191 | + echo -e "\n\n====== Cantera config.log ======" |
| 192 | + echo "Found at: $cantera_config_log" |
| 193 | + cat "$cantera_config_log" |
| 194 | + # Copy to a known location for artifact upload |
| 195 | + mkdir -p /tmp/cantera-logs |
| 196 | + cp "$cantera_config_log" /tmp/cantera-logs/config.log |
| 197 | + else |
| 198 | + echo "Cantera config.log not found" |
| 199 | + echo "Searching in all /private/tmp directories:" |
| 200 | + find /private/tmp -name "config.log" 2>/dev/null || echo "No config.log files found" |
| 201 | + fi |
| 202 | + |
| 203 | + - name: Upload Homebrew logs on failure |
| 204 | + if: failure() |
| 205 | + uses: actions/upload-artifact@v4 |
| 206 | + with: |
| 207 | + name: homebrew-logs |
| 208 | + path: | |
| 209 | + /tmp/brew-install.log |
| 210 | + /tmp/cantera-logs/ |
| 211 | + ~/Library/Logs/Homebrew/mfc/ |
| 212 | + if-no-files-found: ignore |
| 213 | + |
| 214 | + - name: Test MFC installation |
| 215 | + run: | |
| 216 | + echo "=== Testing MFC Installation ===" |
| 217 | + |
| 218 | + echo "1. Checking binaries exist and are executable..." |
| 219 | + test -f $(brew --prefix)/bin/mfc && test -x $(brew --prefix)/bin/mfc |
| 220 | + test -f $(brew --prefix)/bin/pre_process && test -x $(brew --prefix)/bin/pre_process |
| 221 | + test -f $(brew --prefix)/bin/simulation && test -x $(brew --prefix)/bin/simulation |
| 222 | + test -f $(brew --prefix)/bin/post_process && test -x $(brew --prefix)/bin/post_process |
| 223 | + echo " ✓ All binaries exist and are executable" |
| 224 | + |
| 225 | + echo "2. Verifying installation structure..." |
| 226 | + test -f $(brew --prefix mfc)/libexec/mfc.sh |
| 227 | + test -d $(brew --prefix mfc)/toolchain |
| 228 | + echo " ✓ Installation structure verified" |
| 229 | + |
| 230 | + echo "3. Checking Python venv..." |
| 231 | + test -d $(brew --prefix mfc)/libexec/venv |
| 232 | + test -f $(brew --prefix mfc)/libexec/venv/bin/python |
| 233 | + test -f $(brew --prefix mfc)/libexec/venv/bin/pip |
| 234 | + echo " ✓ Python venv exists" |
| 235 | + |
| 236 | + echo "4. Checking examples..." |
| 237 | + test -d $(brew --prefix mfc)/examples |
| 238 | + test -f $(brew --prefix mfc)/examples/1D_sodshocktube/case.py |
| 239 | + echo " ✓ Examples installed" |
| 240 | + |
| 241 | + echo "5. Testing mfc wrapper..." |
| 242 | + mfc --help |
| 243 | + echo " ✓ mfc --help succeeded" |
| 244 | + |
| 245 | + echo "=== All tests passed! ===" |
| 246 | + |
| 247 | + - name: Run MFC test case |
| 248 | + run: | |
| 249 | + echo "Running a simple test case (1D Sod shock tube)..." |
| 250 | + TESTDIR=$(mktemp -d) |
| 251 | + cp $(brew --prefix mfc)/examples/1D_sodshocktube/case.py "$TESTDIR/" |
| 252 | + |
| 253 | + echo "Running with $(sysctl -n hw.ncpu) processors..." |
| 254 | + # Use absolute path since mfc wrapper creates its own tmpdir |
| 255 | + mfc run "$TESTDIR/case.py" -j $(sysctl -n hw.ncpu) |
| 256 | + |
| 257 | + echo "Test case completed successfully!" |
| 258 | + |
| 259 | + - name: Uninstall and cleanup |
| 260 | + if: always() |
| 261 | + run: | |
| 262 | + echo "Cleaning up..." |
| 263 | + brew uninstall mfc || true |
| 264 | + brew cleanup |
0 commit comments