콘다(conda) + direnv

많은 프로젝트들이 디렉토리 단위로 파일과 의존성을 관리합니다. direnv는 디렉토리 단위로 각종 환경 변수를 자동으로 변경해주는 기특한 녀석입니다. 사용법도 간단하고, 효과도 좋습니다. 가상환경을 많이 사용하는 파이선(Python)은 물론 설정이 좀 딱딱한 느낌의 고(Go) 프로그래밍 언어와도 잘 어울립니다.

1. direnv

direnv 사이트에 들어가보면 간단하고 충분한 사용 예가 나옵니다. 쭉 따라가며 주석으로 설명해보겠습니다.

# 프로젝트 디렉토리로 이동
$ cd ~/my_project
# FOO 라는 변수가 선언되지 않은 것을 확인
$ echo ${FOO-nope}
nope
# FOO 변수의 값을 foo 로 선언하고 .envrc 파일에 저장.
# .envrc 파일 내용이 적용되지 않음 메시지 표시됨
$ echo export FOO=foo > .envrc
.envrc is not allowed
# .envrc 파일 내용을 적용하는 명령. direnv allow
$ direnv allow .
direnv: reloading
direnv: loading .envrc
direnv export: +FOO
# FOO 변수의 값이 foo 임을 확인
$ echo ${FOO-nope}
foo
# 프로젝트 디렉토리에서 빠져나옴
$ cd ..
direnv: unloading
direnv export: ~PATH
# FOO 변수가 선언되지 않음을 확인
$ echo ${FOO-nope}
nope

결국 .envrc 파일만 프로젝트에 맞게 설정하면, cd 명령으로 direnv가 시동되어 자연스럽게 원하는 환경으로 바뀝니다. 이제 파이선 가상환경과 어떻게 연동되는지 확인해봅시다.

2. 가상환경과 direnv

direnv는 직접 파이선 가상환경을 지원하지 않습니다. 대신 공식 위키에 있는 자세한 설명을 따라하기만 하면 됩니다.

할 일은 세 가지 입니다.

  1. 레이아웃 (layout) 정의
  2. 프로젝트 디렉토리에 환경설정
  3. 쉘 함수 정의

2.1 레이아웃 (layout) 정의

$HOME/.direnvrc 파일을 열고 (없으면 생성해서) 아래 내용을 추가합니다. layout_conda 라는 함수를 정의했으니 이제 conda라는 이름의 레이아웃(layout)을 사용할 수 있습니다.

layout_conda() {
  local CONDA_HOME="${HOME}/miniconda3/"
  PATH_add "$CONDA_HOME"/bin

  if [ -n "$1" ]; then
    # Explicit environment name from layout command.
    local env_name="$1"
    source activate ${env_name}
  elif (grep -q name: environment.yml); then
    # Detect environment name from `environment.yml` file in `.envrc` directory
    source activate `grep name: environment.yml | sed -e 's/name: //'`
  else
    (>&2 echo No environment specified);
    exit 1;
  fi;
}

2.2 프로젝트 디렉토리에 환경 설정

$PROJECT_HOME/.envrc 파일을 열어서 (없으면 생성해서) 다음 내용을 추가합니다. 여기서는 가상환경의 이름을 hello 라고 했습니다.

layout conda hello

이제 디렉토리를 들어갔다 (cd $PROJECT_HOME) 나왔다 (cd ..) 해봅시다. 그런데, 아무일도 일어나지 않는다면 hook 이 필요합니다.

배시라면 .bashrc 파일에 내용을 넣고 source .bashrc 명령으로 초기 환경설정을 다시 합니다.

eval "$(direnv hook bash)"

Z 쉘이라면 .zshrc 파일에 내용을 넣고 source .zshrc 명령으로 초기 환경설정을 다시 합니다.

eval "$(direnv hook zsh)"

이제 프로젝트 디렉토리에 들어가면 친절한 빨간 경고가 보입니다. direnv allow를 해주는 것으로 끝입니다.

$ cd hello
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.

hello $ direnv allow
direnv: loading .envrc
direnv: export +CONDA_DEFAULT_ENV +CONDA_EXE +CONDA_PREFIX +CONDA_PROMPT_MODIFIER +CONDA_PYTHON_EXE +CONDA_SHLVL -DYLD_LIBRARY_PATH ~PATH

hello $ python -c "import flask; print(flask.__version__)"
1.0.2
hello $ cd ..
direnv: unloading

$ python -c "import flask; print(flask.__version__)"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'flask'

이제 원하는데로 프로젝트 디렉토리에 들어가면 플라스크를 사용할 수 있고 프로젝트를 나오면 플라스크를 사용할 수 없는 것으 ㄹ확인할 수 있습니다. 하지만 뭔가 이상하지 않나요? 그렇습니다. source activate를 할때는 잘 표시되던 프롬프트의 “(가상환경 이름)” 항목이 보이지 않습니다.

2.3 쉘 함수 정의

이 문제도 wiki에 잘 설명되어있습니다. 우선 설명된 해결 방법은, 배시 쉘은 $HOME/.bashrc, Z 쉘은 $HOME/.zshrc 파일을 열고 아래 내용을 추가합니다.

# $HOME/.bashrc
show_virtual_env() {
  if [ -n "$VIRTUAL_ENV" ]; then
    echo "($(basename $VIRTUAL_ENV))"
  fi
}
export -f show_virtual_env
PS1='$(show_virtual_env)'$PS1

내용은 단순합니다. $VIRTUAL_ENV 변수가 있으면 프롬프트에 표시하고 없으면 표시하지 않습니다. 그런데 콘다는 다른 변수, $CONDA_DEFAULT_ENV 를 사용합니다. 저는 virtualenv 기반의 pipenv 와 conda 를 둘 다 사용하고 있기 때문에 아래와 같이 조금 수정했습니다, 가상환경 이름도 노란 색으로 표시해봤습니다.

# $HOME/.zshrc
autoload -U colors && colors
show_virtual_env() {
  if [ -n "$VIRTUAL_ENV" ]; then
    echo "($(basename $VIRTUAL_ENV))"
  elif [ -n "$CONDA_DEFAULT_ENV" ]; then
    echo "($(basename $CONDA_DEFAULT_ENV))"
  fi
}
export -f show_virtual_env
PS1='%{$fg[yellow]%}$(show_virtual_env)%{$reset_color%}'$PS1

3. 마무리

direnv 도 최고의 툴은 아닙니다. 마음대로, 예상한대로 동작하지 않는 기능도 있습니다. 하지만, 적절하게 사용한다면 여러분의 작업효율에 큰 도움이 되리라 생각합니다.