`mjpython` bug when installed with `uv` package manager
Hi,
When i try to run a MuJoCo script with mjpython on my Macbook after installing mujoco with the uv package manager, I get an error message. To reproduce the error, I ran the following commands:
# Install `uv` on macOS and Linux.
$ curl -LsSf https://astral.sh/uv/install.sh | sh
# Create project and run mjpython
uv init mujoco-example
cd mujoco-example
uv add mujoco
source .venv/bin/activate
mjpython
The error says the following:
(.venv) jonzamora@mbp mujoco-example % mjpython
failed to dlopen path '/Users/jonzamora/Research/mujoco-example/.venv/bin/python': dlopen(/Users/jonzamora/Research/mujoco-example/.venv/bin/python, 0x000A): Library not loaded: @executable_path/../lib/libpython3.10.dylib
Referenced from: <4C4C44EE-5555-3144-A1A0-28739E256A39> /Users/jonzamora/.local/share/uv/python/cpython-3.10.14-macos-aarch64-none/bin/python3.10
Reason: tried: '/Users/jonzamora/Research/mujoco-example/.venv/lib/python3.10/site-packages/mujoco/MuJoCo (mjpython).app/Contents/lib/libpython3.10.dylib' (no such file), '/Users/jonzamora/Research/mujoco-example/.venv/bin/../lib/libpython3.10.dylib' (no such file), '/libpython3.10.dylib' (no such file)
Of course, you could run mjpython main.py, where main.py is a passive viewer rendering script like this:
import time
import mujoco
import mujoco.viewer
m = mujoco.MjModel.from_xml_path('/path/to/mjcf.xml')
d = mujoco.MjData(m)
with mujoco.viewer.launch_passive(m, d) as viewer:
# Close the viewer automatically after 30 wall-seconds.
start = time.time()
while viewer.is_running() and time.time() - start < 30:
step_start = time.time()
# mj_step can be replaced with code that also evaluates
# a policy and applies a control signal before stepping the physics.
mujoco.mj_step(m, d)
# Example modification of a viewer option: toggle contact points every two seconds.
with viewer.lock():
viewer.opt.flags[mujoco.mjtVisFlag.mjVIS_CONTACTPOINT] = int(d.time % 2)
# Pick up changes to the physics state, apply perturbations, update options from GUI.
viewer.sync()
# Rudimentary time keeping, will drift relative to wall clock.
time_until_next_step = m.opt.timestep - (time.time() - step_start)
if time_until_next_step > 0:
time.sleep(time_until_next_step)
Any help will be greatly appreciated, i'm sure others are also hoping to use a combination of mujoco and uv on their Macbook machines. Thanks!
My Setup
- Operating system + Machine
- macOS Sonoma, M3 Max Macbook Pro
- MuJoCo version (and if the bug is new, the version where it used to work)
- MuJoCo 3.2.2
- For Python issues, what bindings are you using (i.e
mujoco,dm_control,mujoco-py)?-
mujoco
-
For comparison, if i create a mamba / conda environment and pip install mujoco, then run mjpython, the expected behavior occurs:
(mujoco) jonzamora@mbp mujoco-example % mjpython
Python 3.10.14 | packaged by conda-forge | (main, Mar 20 2024, 12:51:49) [Clang 16.0.6 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
I'm not familiar with uv but it seems that for whatever reason this section of code isn't working properly.
Would you be able to run get_executable_path() from mjpython.py from your non-working venv and tell me what you get?
I'm facing similar issue when creating venv via Bazel and rules_uv.
ipdb> get_executable_path()
/Users/user/.venv/bin/python3
$ mjpython
failed to dlopen path '/Users/user/.venv/bin/python3': dlopen(/Users/user/.venv/bin/python3, 0x000A): Library not loaded: @executable_path/../lib/libpython3.12.dylib
Referenced from: <4C4C44EA-5555-3144-A121-B75170B036A4> /private/var/tmp/_bazel_user/4e70935c28346adf27d1290baa381158/external/rules_python~~python~python_3_12_aarch64-apple-darwin/bin/python3.12
Reason: tried: '/Users/user/.venv/lib/python3.12/site-packages/mujoco/MuJoCo_(mjpython).app/Contents/lib/libpython3.12.dylib' (no such file), '/Users/user/.venv/bin/../lib/libpython3.12.dylib' (no such file), '/libpython3.12.dylib' (no such file)
libpython3.12.dylib does indeed exist relative to the bazel python (in /private/var/tmp/_bazel_user/4e70935c28346adf27d1290baa381158/external/rules_python~~python~python_3_12_aarch64-apple-darwin/bin/../lib/libpython3.12.dylib) but not relative to .venv python (/Users/user/.venv/bin/../lib/libpython3.12.dylib)
$ ls "/private/var/tmp/_bazel_user/4e70935c28346adf27d1290baa381158/external/rules_python~~python~python_3_12_aarch64-apple-darwin/bin/../lib/libpython3.12.dylib"
/private/var/tmp/_bazel_user/4e70935c28346adf27d1290baa381158/external/rules_python~~python~python_3_12_aarch64-apple-darwin/bin/../lib/libpython3.12.dylib
I'm not sure what the right solution here would be. I tried poking around get_executable_path and here are a couple of ways I found to reference the Bazel python location:
$ diff -ruN .venv/bin/mjpython-old .venv/bin/mjpython
--- .venv/bin/mjpython-old
+++ .venv/bin/mjpython
@@ -28,6 +28,7 @@
import re
import subprocess
import sys
+from sysconfig import get_config_var
if platform.system() != 'Darwin':
raise RuntimeError('This script only works on macOS')
@@ -68,6 +69,9 @@
capture_output=True,
check=True,
).stdout.decode()
+
+ project_base = get_config_var("projectbase")
+
for line in otool_out.split('\n'):
m = pattern.search(line)
if m is not None:
@@ -75,6 +79,11 @@
if new_path not in dyld_fallback_paths:
dyld_fallback_paths.insert(0, new_path)
+ if project_base is not None:
+ new_path = os.path.dirname(os.path.join(project_base, m.group(1)))
+ if new_path not in dyld_fallback_paths:
+ dyld_fallback_paths.insert(0, new_path)
+
os.environ['DYLD_FALLBACK_LIBRARY_PATH'] = ':'.join(dyld_fallback_paths)
# argv[0] is currently the path to this script.
Alternatively one could use os.path.dirname(sysconfig.get_path('stdlib')), which, at least in my case points to the right place.
There are four values in sysconfig that reference the Bazel path:
ipdb> get_config_var("installed_base")
'/private/var/tmp/_bazel_user/4e70935c28346adf27d1290baa381158/external/rules_python~~python~python_3_12_aarch64-apple-darwin'
ipdb> get_config_var("installed_platbase")
'/private/var/tmp/_bazel_user/4e70935c28346adf27d1290baa381158/external/rules_python~~python~python_3_12_aarch64-apple-darwin'
ipdb> get_config_var("projectbase")
'/private/var/tmp/_bazel_user/4e70935c28346adf27d1290baa381158/external/rules_python~~python~python_3_12_aarch64-apple-darwin/bin'
ipdb> get_config_var("srcdir")
'/private/var/tmp/_bazel_user/4e70935c28346adf27d1290baa381158/external/rules_python~~python~python_3_12_aarch64-apple-darwin/lib/python3.12/config-3.12-darwin'
I'm not sure if any of these are good to rely on.
In case someone else faces this, here's how I've managed to work around this:
$ DYLD_FALLBACK_LIBRARY_PATH=./bazel-bin/create_venv.runfiles/rules_python\~\~python\~python_3_12_aarch64-apple-darwin/lib \
mjpython
Where create_venv.runfiles should be replaced with the venv creation target name (e.g. [your-target].runfiles).
Here is a quick hack for this problem:
# Find the path to LIBDIR and LDLIBRARY (dylib file) on your system:
PYTHON_LIB_DIR=$(python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LIBDIR"))')
PYTHON_LIB_NAME=$(python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LDLIBRARY"))')
# Soft link it inside your virtual environment:
ln -s "$PYTHON_LIB_DIR/$PYTHON_LIB_NAME" ./.venv/lib/$PYTHON_LIB_NAME
Can confirm the issue persists (uv==0.8.4, python==3.12.10, mujoco=3.3.4).
Here is the same command from shehab modified for fish shell:
# Find the path to LIBDIR and LDLIBRARY (dylib file) on your system:
set PYTHON_LIB_DIR (python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LIBDIR"))')
set PYTHON_LIB_NAME (python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LDLIBRARY"))')
# Soft link it inside your virtual environment:
ln -s "$PYTHON_LIB_DIR/$PYTHON_LIB_NAME" ./.venv/lib/$PYTHON_LIB_NAME
For anybody meeting this issue, I was able to get around it on Mac OS by ensure my uv venv python version matches the system version (as verified by python --version), for example uv venv --python 3.13.