diff --git a/.github/workflows/bottle.yml b/.github/workflows/bottle.yml index d3b7f8e..6a23fa1 100644 --- a/.github/workflows/bottle.yml +++ b/.github/workflows/bottle.yml @@ -2,11 +2,6 @@ name: Bottle on: push: - branches: - - main - paths: - - "Formula/**" - - ".github/workflows/bottle.yml" workflow_dispatch: permissions: @@ -30,31 +25,28 @@ jobs: - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@master + # IMPORTANT: do NOT untap mflowcode/mfc in this job. setup-homebrew manages it and + # its post-cleanup expects the tap directory to exist. + - name: Ensure no leftover local tap + shell: bash + run: | + set -euo pipefail + brew untap mflowcode/local >/dev/null 2>&1 || true + - name: Determine version and root URL (from checked-out formula) id: meta + shell: bash run: | set -euo pipefail - FORMULA="Formula/mfc.rb" - - # Take the *last* matching URL (guards against accidental duplicates) URL="$( - grep -E '^\s*url\s+"https://github.com/.*/archive/refs/tags/v[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz"' "${FORMULA}" \ - | sed -E 's/^\s*url\s+"([^"]+)".*/\1/' \ + grep -Eo 'https://github.com/[^"]+/archive/refs/tags/v[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz' "${FORMULA}" \ | tail -n 1 )" - - if [[ -z "${URL}" ]]; then - echo "Could not extract release tarball URL from ${FORMULA}" >&2 - echo "Expected a line like: url \"https://github.com///archive/refs/tags/vX.Y.Z.tar.gz\"" >&2 - exit 1 - fi + [[ -n "${URL}" ]] VERSION="$(echo "${URL}" | sed -E 's/.*v([0-9]+\.[0-9]+\.[0-9]+)\.tar\.gz/\1/')" - if [[ -z "${VERSION}" ]]; then - echo "Could not parse version from URL: ${URL}" >&2 - exit 1 - fi + [[ -n "${VERSION}" ]] TAG="mfc-${VERSION}" ROOT_URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}" @@ -68,31 +60,18 @@ jobs: echo "Release tag: ${TAG}" echo "Bottle root_url: ${ROOT_URL}" - - name: Install formula with --build-bottle (from checked-out formula via temp tap) + - name: Install formula with --build-bottle (tap formula) + shell: bash run: | set -euo pipefail - - echo "Installing mfc with --build-bottle from checked-out Formula/mfc.rb via a temporary local tap..." - - # brew tap-new needs git identity sometimes - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - - # Create a local tap and copy the formula into Homebrew's tap location - brew tap-new mflowcode/local - TAP_DIR="$(brew --repository)/Library/Taps/mflowcode/homebrew-local" - mkdir -p "${TAP_DIR}/Formula" - cp Formula/mfc.rb "${TAP_DIR}/Formula/mfc.rb" - - # Homebrew may exit non-zero due to dylib fixup issues in Python wheels, - # even though the formula actually installed correctly. Treat that as - # non-fatal as long as the formula is present. + echo "Installing mfc with --build-bottle (allowing non-fatal dylib fixup failures)..." + set +e - brew install --build-bottle mflowcode/local/mfc 2>&1 | tee /tmp/brew-install.log + brew install --build-bottle mflowcode/mfc/mfc 2>&1 | tee /tmp/brew-install.log brew_exit_code=$? set -e - - if brew list mfc &>/dev/null; then + + if brew list mflowcode/mfc/mfc &>/dev/null; then echo "✅ mfc installed successfully (ignoring dylib fixup warnings)" else echo "❌ mfc installation failed; propagating brew exit code" @@ -100,42 +79,49 @@ jobs: fi - name: Run Sod shock tube test case + shell: bash run: | set -euo pipefail echo "Running a simple test case (1D Sod shock tube) on ${{ matrix.os }}..." - - TESTDIR=$(mktemp -d) - cp "$(brew --prefix mfc)/examples/1D_sodshocktube/case.py" "$TESTDIR/" - + TESTDIR="$(mktemp -d)" + cp "$(brew --prefix mflowcode/mfc/mfc)/examples/1D_sodshocktube/case.py" "$TESTDIR/" cd "$TESTDIR" mfc case.py -j 1 - test -d "$TESTDIR/silo_hdf5" echo "✅ Test case ran successfully and produced output" - name: Basic installation verification + shell: bash run: | set -euo pipefail + PREFIX="$(brew --prefix mflowcode/mfc/mfc)" + echo "1. Checking binaries..." - test -x "$(brew --prefix mfc)/bin/pre_process" - test -x "$(brew --prefix mfc)/bin/simulation" - test -x "$(brew --prefix mfc)/bin/post_process" - test -x "$(brew --prefix mfc)/bin/mfc" + test -x "${PREFIX}/bin/pre_process" + test -x "${PREFIX}/bin/simulation" + test -x "${PREFIX}/bin/post_process" + test -x "${PREFIX}/bin/mfc" + echo "2. Checking toolchain..." - test -d "$(brew --prefix mfc)/toolchain" + test -d "${PREFIX}/toolchain" + echo "3. Checking Python venv..." - test -d "$(brew --prefix mfc)/libexec/venv" - test -x "$(brew --prefix mfc)/libexec/venv/bin/python" + test -d "${PREFIX}/libexec/venv" + test -x "${PREFIX}/libexec/venv/bin/python" + echo "4. Checking examples..." - test -d "$(brew --prefix mfc)/examples" + test -d "${PREFIX}/examples" + echo "5. Testing mfc command..." mfc --help + echo "✅ All verification checks passed on ${{ matrix.os }}" - - name: Build bottle + - name: Build bottle (tap formula) + shell: bash run: | set -euo pipefail - brew bottle --root-url="${{ steps.meta.outputs.root_url }}" --json mfc + brew bottle --root-url="${{ steps.meta.outputs.root_url }}" --json mflowcode/mfc/mfc ls -1 *.bottle.* - name: Upload bottle artifacts @@ -161,57 +147,54 @@ jobs: - name: Determine version and root URL (from checked-out formula) id: meta + shell: bash run: | set -euo pipefail - FORMULA="Formula/mfc.rb" - URL="$( - grep -E '^\s*url\s+"https://github.com/.*/archive/refs/tags/v[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz"' "${FORMULA}" \ - | sed -E 's/^\s*url\s+"([^"]+)".*/\1/' \ + grep -Eo 'https://github.com/[^"]+/archive/refs/tags/v[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz' "${FORMULA}" \ | tail -n 1 )" - - if [[ -z "${URL}" ]]; then - echo "Could not extract release tarball URL from ${FORMULA}" >&2 - exit 1 - fi - + [[ -n "${URL}" ]] + VERSION="$(echo "${URL}" | sed -E 's/.*v([0-9]+\.[0-9]+\.[0-9]+)\.tar\.gz/\1/')" - if [[ -z "${VERSION}" ]]; then - echo "Could not parse version from URL: ${URL}" >&2 - exit 1 - fi - + [[ -n "${VERSION}" ]] + TAG="mfc-${VERSION}" ROOT_URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}" - + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "tag=${TAG}" >> "$GITHUB_OUTPUT" echo "root_url=${ROOT_URL}" >> "$GITHUB_OUTPUT" + - name: Configure git for bottle merge + shell: bash + run: | + set -euo pipefail + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + - name: Download all bottle artifacts uses: actions/download-artifact@v4 with: path: bottles - name: Flatten bottle directory + shell: bash run: | set -euo pipefail echo "Before flattening:" find bottles -name '*.bottle.*' -type f - find bottles -mindepth 2 -name '*.bottle.*' -type f -exec mv {} bottles/ \; - echo "" echo "After flattening:" ls -1 bottles/*.bottle.* - name: Rename bottles for GitHub release + shell: bash run: | set -euo pipefail echo "Renaming bottles (mfc-- → mfc-)..." - for file in bottles/mfc--*.bottle.*; do if [[ -f "$file" ]]; then newname=$(echo "$file" | sed 's/mfc--/mfc-/') @@ -219,67 +202,52 @@ jobs: echo " $file → $newname" fi done - echo "" echo "Bottles ready for upload:" ls -1 bottles/*.bottle.* - name: Merge bottle metadata into formula id: merge + shell: bash run: | set -euo pipefail - + shopt -s nullglob JSON_BOTTLES=(bottles/*.bottle*.json) - if [[ ! -e "${JSON_BOTTLES[0]}" ]]; then + if (( ${#JSON_BOTTLES[@]} == 0 )); then echo "No bottle metadata (*.bottle*.json) found in bottles/" ls -la bottles/ exit 1 fi - + echo "Merging bottle metadata from:" printf '%s\n' "${JSON_BOTTLES[@]}" - - BEFORE_SHA=$(git rev-parse HEAD) - echo "HEAD before merge: ${BEFORE_SHA}" - - brew bottle --merge --write "${JSON_BOTTLES[@]}" - - AFTER_SHA=$(git rev-parse HEAD) - echo "HEAD after merge: ${AFTER_SHA}" - + + BEFORE_SHA="$(git rev-parse HEAD)" + brew bottle --merge --write --root-url="${{ steps.meta.outputs.root_url }}" "${JSON_BOTTLES[@]}" + AFTER_SHA="$(git rev-parse HEAD)" + if [[ "${BEFORE_SHA}" != "${AFTER_SHA}" ]]; then - echo "✅ New bottle commit created by brew bottle --write" + echo "✅ brew bottle --write created commit ${AFTER_SHA}" echo "bottle_updated=true" >> "$GITHUB_OUTPUT" else - echo "ℹ️ No new commit (bottles unchanged or already present)" + echo "ℹ️ No commit created (bottles unchanged or already present)" echo "bottle_updated=false" >> "$GITHUB_OUTPUT" fi - name: Push bottle updates if changed if: steps.merge.outputs.bottle_updated == 'true' + shell: bash run: | set -euo pipefail - - echo "Pushing bottle update commit..." - git log -1 --stat - - git config --local --unset-all http.https://github.com/.extraheader || true - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git - + git remote set-url origin "https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" git push origin HEAD:main - - name: Create or update GitHub Release + - name: Create/update release and upload bottles uses: softprops/action-gh-release@v1 with: tag_name: ${{ steps.meta.outputs.tag }} name: MFC bottles ${{ steps.meta.outputs.version }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload bottles to Release - uses: softprops/action-gh-release@v1 - with: - tag_name: ${{ steps.meta.outputs.tag }} files: bottles/*.bottle.* + fail_on_unmatched_files: true env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} diff --git a/Formula/mfc.rb b/Formula/mfc.rb index 06150ef..69d6014 100644 --- a/Formula/mfc.rb +++ b/Formula/mfc.rb @@ -29,13 +29,11 @@ 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", "setuptools-scm", "hatchling", "hatch-vcs", "editables" - # 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 # Match the version constraint from toolchain/pyproject.toml @@ -47,8 +45,9 @@ def install # Keep toolchain in buildpath for now - mfc.sh needs it there # # MFC's toolchain uses VCS-derived versioning (via Hatch/hatch-vcs) and Homebrew builds from - # GitHub release tarballs without a .git directory. Scope pretend-version env vars tightly - # to avoid polluting subsequent pip installs. + # GitHub release tarballs without a .git directory. Use --no-build-isolation so the build + # backend can see our environment variables, and set SETUPTOOLS_SCM_PRETEND_VERSION which + # hatch-vcs respects when VCS metadata is unavailable. pretend_env = { "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MFC" => version.to_s, "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_mfc" => version.to_s, @@ -61,7 +60,7 @@ def install end begin - system venv/"bin/pip", "install", "-e", buildpath/"toolchain" + system venv/"bin/pip", "install", "--no-build-isolation", "-e", buildpath/"toolchain" ensure pretend_env.each_key do |k| if saved_env[k].nil? @@ -79,7 +78,12 @@ def install # 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 - + + # Also set pretend-version env vars for mfc.sh in case it tries to reinstall toolchain + ENV["SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MFC"] = version.to_s + ENV["SETUPTOOLS_SCM_PRETEND_VERSION_FOR_mfc"] = version.to_s + ENV["SETUPTOOLS_SCM_PRETEND_VERSION"] = version.to_s + # Build MFC using pre-configured venv # Must run from buildpath (MFC root directory) where toolchain/ exists Dir.chdir(buildpath) do