Files
orisys_arm/scripts/lib/conda_switch.sh
T
2026-10-16 17:27:30 +08:00

104 lines
4.5 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <env_name> [<other_env_name>]
# 干净地激活 <env_name>;若当前已在 <other_env_name> 里,先退到 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 <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_ 前缀降低撞名概率。)