仿真环境串味根治:清残留路径 + 补 conda lib + 修入口脚本 shebang
症状一(最隐蔽):同一 shell 里先 source 过真机环境(scripts/env.sh)再
source 仿真环境,install/ 与 ros_flexiv 的路径不会被 conda deactivate 撤掉,
长在 install_sim/ 前面:
- PYTHONPATH 里真机那份 arm_msgs 压住仿真那份 → 消息类型混用
- LD_LIBRARY_PATH 里没有 $CONDA_PREFIX/lib → sim_tactile_adapter 订阅
ros_gz_interfaces/Contacts 时抛
Type support not from this implementation ...
cannot open shared object file
而库其实就在 conda 里、ldd 全齐。整条触觉链静默死掉(话题不发)。
症状二:setuptools 写 console_scripts 用的是**构建那一刻**的 python。
若在真机环境里跑过 build_sim.sh,install_sim/ 里 7 个入口脚本的 shebang
会指向 ros_flexiv —— 而 ros2 run 是照 shebang 执行的,于是**仿真节点一直
跑在真机 conda 环境里**。两个环境都是 Humble+py3.11,所以"居然能跑",
只有偶发 dlopen / 反序列化怪错。
修法:
- conda_switch.sh 新增 path_drop_patterns:按 glob 从冒号列表里删条目
- sim_env.sh 清掉真机残留 + 末尾补 $CONDA_PREFIX/lib(优先级最低)
- 新增 lib/fix_shebangs.sh,build.sh / build_sim.sh 收尾各扫一遍(幂等)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -52,5 +52,52 @@ conda_switch_clean() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# path_drop_patterns <VAR> <glob>...
|
||||
# 从环境变量 <VAR> 的冒号分隔列表里删掉所有匹配任一 glob 的条目(保持剩余顺序)。
|
||||
# 用于清理"同一个 shell 里先 source 过另一套环境"留下的路径残留。
|
||||
#
|
||||
# 为什么必须清:真机环境(scripts/env.sh)与仿真环境(scripts/sim_env.sh)
|
||||
# 共用同一个工作区,各自的 setup.bash 都会往 PATH / PYTHONPATH /
|
||||
# LD_LIBRARY_PATH / AMENT_PREFIX_PATH 里追加自己的产物目录。conda 的
|
||||
# deactivate 只会撤掉 conda 自己那部分,**不会**撤掉 setup.bash 追加的条目。
|
||||
# 于是"先真机后仿真"的 shell 里会长成这样:
|
||||
# PYTHONPATH = .../install/arm_msgs/... : .../install_sim/arm_msgs/...
|
||||
# 此时 `import arm_msgs` 会拿到**真机那份**,与 install_sim 下的运行库混用,
|
||||
# 症状是消息反序列化莫名失败、字段对不上、dlopen 找不到 typesupport ——
|
||||
# 报错信息完全不提"环境脏了"这个真实原因。
|
||||
#
|
||||
# 实测踩过:sim_tactile_adapter 订阅 ros_gz_interfaces/msg/Contacts 时崩在
|
||||
# rcutils_set_error_state: Type support not from this implementation ...
|
||||
# Could not load library libros_gz_interfaces__rosidl_typesupport_fastrtps_c.so
|
||||
# 库其实就在 $CONDA_PREFIX/lib 下(ldd 全齐),根因是 LD_LIBRARY_PATH 里
|
||||
# 既残留着 ros_flexiv 的 lib、又**没有** $CONDA_PREFIX/lib。
|
||||
path_drop_patterns() {
|
||||
local _var="$1"
|
||||
shift
|
||||
local -a _pats=("$@")
|
||||
local _cur="${!_var:-}"
|
||||
[[ -z "${_cur}" ]] && return 0
|
||||
|
||||
local _out="" _entry _pat _hit
|
||||
local IFS=':'
|
||||
for _entry in ${_cur}; do
|
||||
_hit=0
|
||||
for _pat in "${_pats[@]}"; do
|
||||
# 故意不加引号:让右侧当 glob 用(这里就是要通配匹配)
|
||||
# shellcheck disable=SC2053
|
||||
if [[ "${_entry}" == ${_pat} ]]; then
|
||||
_hit=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
[[ "${_hit}" -eq 1 ]] && continue
|
||||
_out="${_out:+${_out}:}${_entry}"
|
||||
done
|
||||
|
||||
printf -v "${_var}" '%s' "${_out}"
|
||||
export "${_var?}"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 供调用方按需取消设置,避免污染使用者的变量空间。
|
||||
# (bash 没有"私有函数"概念,命名上统一加 conda_ 前缀降低撞名概率。)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# 修 console_scripts 的 shebang(真机 / 仿真两套构建产物都会踩的坑)
|
||||
# =============================================================================
|
||||
#
|
||||
# 现象:
|
||||
# source scripts/sim_env.sh && ros2 run arm_sim_gazebo sim_camera_adapter
|
||||
# 进程列表里却是
|
||||
# /home/cc/anaconda3/envs/ros_flexiv/bin/python3.11 ... sim_camera_adapter
|
||||
# 即**仿真节点跑在真机的 conda 环境里**。
|
||||
#
|
||||
# 为什么:
|
||||
# setuptools 的 console_scripts 入口脚本,shebang 用的是**构建那一刻**的
|
||||
# sys.executable。本工作区只有一份源码,真机与仿真各有自己的 conda 环境
|
||||
# (ros_flexiv / ros_gz_sim)和各自的 install 目录。如果在真机环境里跑过
|
||||
# build_sim.sh(或反之),写进 install_sim/ 的脚本 shebang 就指向了另一个
|
||||
# 环境。`ros2 run` 是**照 shebang 执行**的,不看当前 CONDA_PREFIX。
|
||||
#
|
||||
# 为什么危险:
|
||||
# 两个环境都是 ROS 2 Humble + Python 3.11,所以多数时候"居然能跑",
|
||||
# 只是 import 到的是另一个环境的包。表现为偶发的 ABI/`dlopen`/消息反序列化
|
||||
# 报错,报错信息完全不提"shebang 指错环境"。
|
||||
# 实测:LD_LIBRARY_PATH、PYTHONPATH 都清干净了,节点仍然跑在 ros_flexiv 里。
|
||||
#
|
||||
# 对策:
|
||||
# 构建收尾时扫一遍本次产物,把指向"别的 conda 环境"的 shebang 改回
|
||||
# **本环境**的 python。已指向本环境的文件不动(幂等)。
|
||||
#
|
||||
# 用法:
|
||||
# fix_shebangs <install_dir> <expected_python_path>
|
||||
# =============================================================================
|
||||
|
||||
# fix_shebangs <install_dir> <expected_python>
|
||||
# 把 <install_dir> 下所有 console_scripts 里"指向其它 conda 环境"的 shebang
|
||||
# 改写成 <expected_python>。返回 0;打印改了哪些文件(没改就不打印)。
|
||||
fix_shebangs() {
|
||||
local install_dir="$1"
|
||||
local expected="$2"
|
||||
|
||||
if [[ ! -d "${install_dir}" || ! -x "${expected}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 只扫这两类位置:包自带的 console_scripts 目录、以及 bin/。
|
||||
# 用 find 而不是递归 rg:要按"是不是可执行文件 + 首行是 shebang"判断。
|
||||
local -a files=()
|
||||
local f
|
||||
while IFS= read -r -d '' f; do
|
||||
files+=("${f}")
|
||||
done < <(find "${install_dir}" \
|
||||
\( -path "*/lib/*" -o -path "*/bin/*" \) \
|
||||
-type f -perm -u+x -print0 2>/dev/null)
|
||||
|
||||
local fixed=0 first
|
||||
for f in "${files[@]}"; do
|
||||
# 读首行(最多 200 字节);不是 #! 开头就跳过(二进制、数据文件等)
|
||||
first="$(head -c 200 "${f}" | head -1)"
|
||||
[[ "${first:0:2}" == "#!" ]] || continue
|
||||
# 只动"指向某个 conda 环境"的 shebang;/usr/bin/python3 这类系统解释器不动
|
||||
[[ "${first}" == *"/envs/"*"python"* ]] || continue
|
||||
[[ "${first}" == *"${expected}"* ]] && continue
|
||||
|
||||
# 保留原 shebang 里的参数(例如 `python3 -u`)
|
||||
local interp="${first#\#!}" # 去掉 #!
|
||||
interp="${interp%% *}" # 第一个 token = 解释器路径
|
||||
local rest="${first#"#!${interp}"}" # 剩下的是参数(含前导空格)
|
||||
# 就地重写首行:用 printf + tail 组装,避免 sed -i 在符号链接上的坑
|
||||
local tmp="${f}.shebang.$$"
|
||||
{
|
||||
printf '#!%s%s\n' "${expected}" "${rest}"
|
||||
tail -n +2 "${f}"
|
||||
} > "${tmp}" && cat "${tmp}" > "${f}" && rm -f "${tmp}"
|
||||
echo " 修正 shebang: ${f#$PWD/} → ${expected}"
|
||||
fixed=$((fixed + 1))
|
||||
done
|
||||
|
||||
if [[ "${fixed}" -gt 0 ]]; then
|
||||
echo " (共修正 ${fixed} 个入口脚本:它们的 shebang 原本指向别的 conda 环境,"
|
||||
echo " 会导致 ros2 run 起来的节点跑在错误的环境里)"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user