Skip to content

Instantly share code, notes, and snippets.

@matangover
Last active April 9, 2024 18:12
Show Gist options
  • Save matangover/a34c31f7c832a6896795fc842ef26a1e to your computer and use it in GitHub Desktop.
Save matangover/a34c31f7c832a6896795fc842ef26a1e to your computer and use it in GitHub Desktop.
Allow Python on macOS M1 to import native libraries installed using Homebrew

Your Problem

  1. You are using Python on macOS M1 (or M2, etc) and are trying to install a Python library which uses a native library.
  2. You install the Python library (e.g. using pip) and the native library (using Homebrew).
  3. You get an error saying that the native library cannot be located even though it's installed using Homebrew.

Example: You are trying to use e.g. pyfluidsynth. You've installed pyfluidsynth with pip, and also ran brew install fluidsynth. However, import fluidsynth still gives an error:

ImportError: Couldn't find the FluidSynth library.

Explanation

What happens is that pyfluidsynth uses ctypes.find_library to locate the native fluidsynth library. However, find_library doesn't find libraries installed by Homebrew on M1 because Homebrew installs libraries in /opt/homebrew/lib (instead of /usr/local/bin used for Intel Macs) which is not in the default library search path.

Solutions

In order to solve this, you must tell Python to look in /opt/homebrew/lib. Use any of the following solutions depending on your setup. If you are using pyenv, jump straight to solution 3.

Solution 1 - Homebrew Python

Use Python installed by Homebrew (brew install python). Homebrew patches ctypes itself in their version of Python, so that native libraries installed using Homebrew are found by find_library.

This solution won't work if you want to use pyenv or any other installation of Python. It will only work with Homebrew-installed Python. You can apply the same patch to your own installation of Python, but if you use pyenv and multiple versions of Python this becomes quite annoying.

Solution 2 - DYLD_FALLBACK_LIBRARY_PATH

You can set an environment variable that will make ctypes.find_library look in Homebrew's library path:

export DYLD_FALLBACK_LIBRARY_PATH="/opt/homebrew/lib:$DYLD_FALLBACK_LIBRARY_PATH"

You can add this line to your shell profile (~/.zprofile) so that it happens in every shell. Restart your shell for the changes to take effect.

This solution still won't work if you use pyenv. Why? Because of a macOS security feature called SIP which prevents the variable DYLD_FALLBACK_LIBRARY_PATH from being forwarded to 'protected processes' such as the shell. And since pyenv uses a shell script to start Python, it won't get the new DYLD_FALLBACK_LIBRARY_PATH. You may be able to bypass this by partially disabling SIP in Recovery Mode.

Solution 3 - Usercustomize Script

Python executes a script called usercustomize every time it starts up. We can use that to make sure DYLD_FALLBACK_LIBRARY_PATH is set correctly in every Python process. We can even do that for all installed Python versions in a single place. And it works even with pyenv.

Create a usercustomize.py file: ~/.local/lib/global-packages/usercustomize.py. Paste in it the following script:

import os

homebrew_lib_dir = "/opt/homebrew/lib"
existing_lib_path = os.environ.get("DYLD_FALLBACK_LIBRARY_PATH", "")
os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = homebrew_lib_dir + ":" + existing_lib_path

Then, make this usercustomize script be used by any Python interpreter (regardless of version): In your ~/.zprofile add the following line:

export PYTHONPATH="$HOME/.local/lib/global-packages:$PYTHONPATH"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment