Skip to content

Commit d39962a

Browse files
committed
fix(homebrew): move venv to var/ to avoid dylib fixups and harden bin permissions
- Move Python venv from libexec/ to var/mfc/venv to keep it outside the keg This prevents Homebrew's Mach-O dylib ID fixup from touching Python .so files - Write wrapper script directly to bin/mfc with 0755 permissions - Explicitly chmod 0755 on all installed binaries to ensure executability - Update tests to check var/mfc/venv instead of libexec/venv - Symlink venv at runtime instead of copying (faster, less disk usage) These changes resolve: - 'Failed changing dylib ID of orjson.cpython-312-darwin.so' errors - 'Non-executables were installed to bin' audit failures
1 parent 5d2377e commit d39962a

File tree

1 file changed

+64
-73
lines changed

1 file changed

+64
-73
lines changed

packaging/homebrew/mfc.rb

Lines changed: 64 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@ class Mfc < Formula
2121
depends_on "openblas"
2222
depends_on "[email protected]"
2323

24-
# Preserve venv RECORD files (needed for pip to manage packages)
25-
skip_clean "libexec/venv"
26-
2724
def install
2825
# Create Python virtual environment (remove existing one first for clean reinstalls)
29-
venv = libexec/"venv"
26+
# Store in var/ instead of keg to avoid Homebrew dylib fixups on Python .so files
27+
venv = var/"mfc/venv"
28+
mkdir_p venv.parent
3029
rm_r(venv, force: true)
3130
system Formula["[email protected]"].opt_bin/"python3.12", "-m", "venv", venv
3231
system venv/"bin/pip", "install", "--upgrade", "pip", "setuptools", "wheel"
@@ -36,9 +35,7 @@ def install
3635

3736
# Install MFC Python package and dependencies into venv
3837
# Keep toolchain in buildpath for now - mfc.sh needs it there
39-
# Use editable install (-e) to avoid RECORD file issues when venv is copied
40-
# Note: Homebrew may warn about dylib fixup failures for some Python packages (e.g., orjson)
41-
# This is non-fatal since the venv is copied (not linked) at runtime
38+
# Use editable install (-e) to avoid RECORD file issues when venv is symlinked at runtime
4239
system venv/"bin/pip", "install", "-e", buildpath/"toolchain"
4340

4441
# Create symlink so mfc.sh uses our pre-installed venv
@@ -63,6 +60,7 @@ def install
6360
next if !File.file?(p) || !File.executable?(p)
6461

6562
bin.install p
63+
(bin/File.basename(p)).chmod 0755
6664
end
6765

6866
# Install main mfc.sh script
@@ -72,72 +70,65 @@ def install
7270
prefix.install "examples"
7371

7472
# Create smart wrapper script that:
75-
# 1. Works around read-only Cellar issue by copying venv to tmpdir
76-
# 2. Sets up toolchain symlink so mfc.sh can find toolchain/util.sh
73+
# 1. Works around read-only Cellar issue by using a temp working dir
74+
# 2. Uses the persistent var-based venv (no copy needed)
7775
# 3. Ensures mfc.sh doesn't reinstall packages by copying pyproject.toml
78-
79-
# Create wrapper script in buildpath first, then install it
80-
wrapper_script = buildpath/"mfc_wrapper"
81-
wrapper_script.write <<~EOS
82-
#!/bin/bash
83-
set -euo pipefail
84-
85-
# Unset VIRTUAL_ENV to ensure mfc.sh uses the copied venv, not the Cellar one
86-
unset VIRTUAL_ENV || true
87-
88-
# Create a temporary working directory (Cellar is read-only)
89-
TMPDIR="$(mktemp -d)"
90-
trap 'rm -rf "${TMPDIR}"' EXIT
91-
92-
# Copy mfc.sh to temp dir (it may try to write build artifacts)
93-
cp "#{libexec}/mfc.sh" "${TMPDIR}/"
94-
cd "${TMPDIR}"
95-
96-
# Copy toolchain directory (not symlink) so Python paths resolve correctly
97-
# This prevents paths from resolving back to read-only Cellar
98-
cp -R "#{prefix}/toolchain" "toolchain"
99-
100-
# Patch toolchain to use Homebrew-installed binaries
101-
# Replace get_install_binpath to return Homebrew bin directory
102-
cat >> "toolchain/mfc/build.py" << 'PATCH_EOF'
103-
104-
# Homebrew patch: Override get_install_binpath to use pre-installed binaries
105-
_original_get_install_binpath = MFCTarget.get_install_binpath
106-
def _homebrew_get_install_binpath(self, case):
107-
return "#{bin}/" + self.name
108-
MFCTarget.get_install_binpath = _homebrew_get_install_binpath
109-
110-
# Override is_buildable to skip building main targets and syscheck
111-
_original_is_buildable = MFCTarget.is_buildable
112-
def _homebrew_is_buildable(self):
113-
if self.name in ["pre_process", "simulation", "post_process", "syscheck"]:
114-
return False # Skip building - use pre-installed binaries
115-
return _original_is_buildable(self)
116-
MFCTarget.is_buildable = _homebrew_is_buildable
117-
PATCH_EOF
118-
119-
# Copy examples directory (required by mfc.sh Python code)
120-
cp -R "#{prefix}/examples" "examples"
121-
122-
# Create build directory and copy venv (not symlink - needs to be writable)
123-
# Use cp -R for a full recursive copy
124-
mkdir -p "build"
125-
cp -R "#{venv}" "build/venv"
126-
127-
# Copy pyproject.toml to build/ so mfc.sh thinks dependencies are already installed
128-
cp "#{prefix}/toolchain/pyproject.toml" "build/pyproject.toml"
129-
130-
# For 'mfc run', add --no-build flag to skip compilation
131-
if [ "${1-}" = "run" ]; then
132-
exec ./mfc.sh "$@" --no-build
133-
else
134-
exec ./mfc.sh "$@"
135-
fi
76+
(bin/"mfc").write <<~EOS
77+
#!/bin/bash
78+
set -euo pipefail
79+
80+
# Unset VIRTUAL_ENV to ensure mfc.sh uses our configured venv
81+
unset VIRTUAL_ENV || true
82+
83+
# Create a temporary working directory (Cellar is read-only)
84+
TMPDIR="$(mktemp -d)"
85+
trap 'rm -rf "${TMPDIR}"' EXIT
86+
87+
# Copy mfc.sh to temp dir (it may try to write build artifacts)
88+
cp "#{libexec}/mfc.sh" "${TMPDIR}/"
89+
cd "${TMPDIR}"
90+
91+
# Copy toolchain directory (not symlink) so Python paths resolve correctly
92+
# This prevents paths from resolving back to read-only Cellar
93+
cp -R "#{prefix}/toolchain" "toolchain"
94+
95+
# Patch toolchain to use Homebrew-installed binaries
96+
# Replace get_install_binpath to return Homebrew bin directory
97+
cat >> "toolchain/mfc/build.py" << 'PATCH_EOF'
98+
99+
# Homebrew patch: Override get_install_binpath to use pre-installed binaries
100+
_original_get_install_binpath = MFCTarget.get_install_binpath
101+
def _homebrew_get_install_binpath(self, case):
102+
return "#{bin}/" + self.name
103+
MFCTarget.get_install_binpath = _homebrew_get_install_binpath
104+
105+
# Override is_buildable to skip building main targets and syscheck
106+
_original_is_buildable = MFCTarget.is_buildable
107+
def _homebrew_is_buildable(self):
108+
if self.name in ["pre_process", "simulation", "post_process", "syscheck"]:
109+
return False # Skip building - use pre-installed binaries
110+
return _original_is_buildable(self)
111+
MFCTarget.is_buildable = _homebrew_is_buildable
112+
PATCH_EOF
113+
114+
# Copy examples directory (required by mfc.sh Python code)
115+
cp -R "#{prefix}/examples" "examples"
116+
117+
# Create build directory and symlink the persistent venv
118+
mkdir -p "build"
119+
ln -s "#{var}/mfc/venv" "build/venv"
120+
121+
# Copy pyproject.toml so mfc.sh thinks dependencies are already installed
122+
cp "#{prefix}/toolchain/pyproject.toml" "build/pyproject.toml"
123+
124+
# For 'mfc run', add --no-build flag to skip compilation
125+
if [ "${1-}" = "run" ]; then
126+
exec ./mfc.sh "$@" --no-build
127+
else
128+
exec ./mfc.sh "$@"
129+
fi
136130
EOS
137-
138-
# Make wrapper executable and install it
139-
wrapper_script.chmod 0755
140-
bin.install wrapper_script => "mfc"
131+
(bin/"mfc").chmod 0755
141132
end
142133

143134
def post_install
@@ -177,8 +168,8 @@ def caveats
177168
assert_path_exists prefix/"toolchain"
178169

179170
# Test that venv exists and has required packages
180-
assert_path_exists libexec/"venv"
181-
assert_predicate libexec/"venv/bin/python", :executable?
171+
assert_path_exists var/"mfc/venv"
172+
assert_predicate (var/"mfc/venv/bin/python"), :executable?
182173

183174
# Test that examples exist
184175
assert_path_exists prefix/"examples"

0 commit comments

Comments
 (0)