#!/usr/bin/env bash # ============================================================================= # conda 环境切换的公共逻辑。被 scripts/env.sh 与 scripts/sim_env.sh 共用。 # # 为什么需要这个:conda 的 activate 是**堆叠**的。在同一个 shell 里反复 # activate / 切换不同环境,会不断往 PATH、PYTHONPATH 里追加条目,而不会 # 清掉前一个环境留下的。实测的后果非常隐蔽: # # 在同一个 shell 里先 activate ros_gz_sim 再切 ros_flexiv 之后, # `which python` 指向 ros_flexiv/bin/python, # 但 `python -m pip` 却解析到 ros_gz_sim 的 pip! # 于是 colcon --symlink-install 报: # error: option --editable not recognized # 这个报错和真实原因(shell 脏了)毫无关系,极易被误判成 # "环境被搞坏了 / setuptools 版本不对",进而去做一堆无用的修复。 # # 对策:切换前先把堆叠的激活全部退掉(退到 base),再激活目标环境。 # ============================================================================= # conda_deactivate_all: 退到 base(CONDA_SHLVL=1)。任何环境下调用都安全。 conda_deactivate_all() { local guard=0 # CONDA_SHLVL 是 conda 自己维护的激活层数;base 为 1。 # 加 guard 是为了防止某些畸形状态下无限循环。 while [[ "${CONDA_SHLVL:-1}" -gt 1 && "${guard}" -lt 20 ]]; do conda deactivate 2>/dev/null || break guard=$((guard + 1)) done return 0 } # conda_switch_clean [] # 干净地激活 ;若当前已在 里,先退到 base 再切, # 并提示用户最好开新 shell。 # 返回 0 = 成功;非 0 = 目标环境不存在。 conda_switch_clean() { local target="$1" local other="${2:-}" if [[ -n "${other}" && "${CONDA_DEFAULT_ENV:-}" == "${other}" ]]; then echo "提示: 当前 shell 激活的是 ${other},即将切到 ${target}。" >&2 echo " conda 的激活是堆叠的,同一 shell 里来回切会残留 PATH/PYTHONPATH," >&2 echo " 典型症状是构建报 \"--editable not recognized\" 这类与真实原因无关的错。" >&2 echo " 这里会先退到 base 再切以规避;但最稳妥仍是开一个新 shell。" >&2 fi conda_deactivate_all if ! conda activate "${target}" 2>/dev/null; then return 1 fi return 0 } # path_drop_patterns ... # 从环境变量 的冒号分隔列表里删掉所有匹配任一 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_ 前缀降低撞名概率。)