Canvas 1 Layer 1

direnv로 디렉토리(프로젝트) 별 개발환경 구축하기
루비(Ruby), 파이썬(Python), 노드(Node) 개발 환경 구축

들어가며

컴퓨터를 사용하는 환경은 크게 그래피컬 사용자 인터페이스GUI, Graphical User Interface와 커맨드라인 인터페이스CLI, Command Line Interface로 나눠집니다. 프로그래머나 시스템 관리자는 GUI 못지 않게 CLI 환경을 자주 사용합니다. 셸은 커맨드라인에서 대화형으로 명령어를 실행할 수 있도록 도와주는 프로그램으로 CLI와 동의어라고 해도 좋을 정도의 지위를 가지고 있습니다.

일반적으로 셸을 사용하는 경우 사용자 별로 환경이 구축됩니다. 배시 셸을 사용하는 경우 로그인하는 시점에 ~/.bashrc, ~/.bash_profile, ~/.profile과 같은 파일들을 읽어서 사용자의 환경을 구축합니다. 셸을 가끔씩만 사용하는 경우에는 이러한 설정 파일들을 통해서 원하는 환경을 구축하는 데 어려움이 없습니다. 하지만 셸을 사용해 시스템 관리를 비롯해 다수의 프로젝트들을 관리하는 경우 이 설정 파일들이 복잡해지는 문제가 있습니다. 또한 이렇게 작성한 대부분의 설정은 특정한 디렉터리나 프로젝트에서만 의미가 있습니다.

direnv는 바로 이러한 문제를 해결하기 위한 도구입니다. direnv는 사용자가 현재 위치한 디렉터리의 .envrc 파일을 추가로 읽어들여 해당 디렉터리에서만 필요한 설정을 로드합니다. 또한 디렉터리에서 빠져나갈 때는 (가능하다면) 이 설정들을 언로드합니다. 따라서 ~/.profile과 같은 파일에 모든 설정을 몰아넣는 대신 디렉터리 별로 필요한 설정을 정의하는 것이 가능해집니다.

이 글에서는 direnv의 사용법과 프로그래밍 언어와 조합해서 사용하는 방법에 대해서 소개합니다.

direnv 시작하기

direnv를 사용하려면 먼저 설치를 하고 셸에 따라서 적절히 설정을 해야합니다.

설치 및 설정

맥OSmacOS를 사용하는 경우 홈브류를 사용해 쉽게 설치할 수 있습니다.

$ brew install direnv

direnv는 주요 리눅스 배포판의 패키지로도 등록되어있습니다. (다른 패키지들에 대해서는 공식 저장소의 정보를 참고해주세요)

## Ubuntu, Debian
$ apt-get install direnv

## Fedora
$ dnf install direnv

패키지 매니저를 사용하기 어렵다면 릴리즈 페이지에서 바이너리를 다운로드 받아서 사용하거나, 직접 direnv 프로젝트를 컴파일해서 사용하는 것도 가능합니다.

version 명령어로 성공적으로 설치되었는지 확인할 수 있습니다.

$ direnv version
2.14.0

다음으로 자신이 사용하는 셸에 해당하는 내용을 셸의 설정 파일에 추가해줍니다. direnv는 공식적으로 BASH, ZSH, FISH, TCSH, Elvish를 지원합니다.

## BASH -> ~/.bashrc에 아래 내용 추가
eval "$(direnv hook bash)"

## ZSH -> ~/.zshrc에 아래 내용 추가
eval "$(direnv hook zsh)"

## FISH -> ~/.config/fish/config.fish에 아래 내용 추가
eval (direnv hook fish)

## TCSH -> ~/.cshrc에 아래 내용 추가
eval `direnv hook tcsh`

## Elvish -> 아래 내용 실행
$ direnv hook elvish > ~/.elvish/lib/direnv.elv
## ~/.elvish/rc.elv에 아래 내용 추가
use direnv

설정 추가 후 셸을 재실행하면 direnv를 사용할 수 있습니다.

추가적으로 셸 설정 파일에 EDITOR 환경변수를 정의해두면 편리합니다. direnv edit . 명령어는 이 환경변수에 지정된 에디터를 실행합니다.

export EDITOR=emacs

direnv의 기본적인 사용법

곧바로 direnv를 사용해보겠습니다. direnv는 셸 전체에 적용되는 설정 이외에 현재 디렉터리의 특정한 설정을 추가할 수 있도록 도와주는 도구입니다. 따라서 direnv를 사용할 디렉터리를 하나 준비합니다.

$ mkdir direnv
$ cd direnv

direnv를 통해 특정 디렉터리에 적용되는 설정은 .envrc 파일에 셸스크립트로 작성합니다. 이 파일을 임시로 생성합니다.

$ touch .envrc

.envrc 파일이 만들어지면 direnv가 이를 알아채고 경고 메시지를 출력합니다.

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

direnv는 특정 디렉터리의 특정 컨텐츠를 가진 .envrc 파일에 대해서 명시적으로 사용을 허가하지 않은 경우 이 파일을 읽어들이지 않습니다. 이는 의도치않게 원하지 않는 셸 스크립트가 실행되는 것을 방지하기 위해서입니다. 따라서 .envrc 파일을 추가하거나 변경하는 경우 명시적으로 사용을 허가해야만 이 파일을 읽어들입니다.

allow 명령어로 현재 디렉터리의 .envrc 파일의 사용을 허가할 수 있습니다.

$ direnv allow
direnv: loading .envrc

.envrc가 바로 로드되는 것을 알 수 있습니다. 하지만 이 파일에는 아무 내용도 없습니다. 다음 내용을 .envrc에 추가해봅니다.

$ echo 'echo "Hello, direnv"' > .envrc

다시 허가 되지 않은 파일이라고 경고 메시지가 출력됩니다.

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

변경된 내용을 허가해줍니다.

$ direnv allow
direnv: loading .envrc
Hello, direnv

.envrc에 추가한 내용이 정상적으로 실행되는 것을 확인할 수 있습니다.

상위 디렉터리로 이동해봅니다.

$ cd ..
direnv: unloading

.envrc의 내용이 언로딩 되었습니다. 현재 .envrc에는 별 내용이 없기 때문에 특별한 의미는 없습니다. 다시 direnv 디렉터리로 이동해서 .envrc의 내용이 적용되는 것을 확인해보겠습니다.

$ cd direnv
direnv: loading .envrc
Hello, direnv

direnv 디렉터리로 이동하니 다시 .envrc에 작성한 내용이 실행되는 것을 확인할 수 있습니다.

deny 명령어를 사용하면 허가한 내용을 명시적으로 금지하는 것도 가능합니다.

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

direnv에서 allow한 정보는 ~/.config/direnv/allow/에서 관리됩니다. 이 디렉터리의 파일은 .envrc의 경로와 내용을 조합으로 sha256sum으로 작성됩니다.

.envrc에 환경변수 추가하기

셸에서는 환경변수를 통해서 작업 환경이나 애플리케이션 실핼 시에 사용하는 설정을 전달하는 방식을 자주 사용합니다. direnv의 가장 기본적인 활용법은 디렉터리(프로젝트) 별로 환경변수를 정의하는 일입니다.

.envrc 파일에 환경변수를 정의하는 내용을 추가해줍니다.

$ echo 'export HELLO=world' > .envrc

allow 명령어로 .envrc를 허가해줍니다.

$ direnv allow
direnv: loading .envrc
direnv: export +HELLO

direnv는 .envrc를 읽어들이고 추가한 환경변수 목록을 보여줍니다. printenv는 현재 환경에 정의된 환경변수를 확인하는 명령어입니다. printenvgrep으로 새로 추가한 HELLO 환경변수를 찾아봅니다.

$ printenv | grep HELLO
HELLO=world

정상적으로 환경변수가 추가된 것을 확인할 수 있습니다. 상위 디렉터리로 이동해보겠습니다.

$ cd ..
direnv: unloading
$ printenv | grep HELLO
<출력 결과 없음>

.envrc 파일이 언로딩 되고, 더 이상 HELLO 환경변수는 찾을 수 없습니다. 다시 direnv 디렉터리로 이동해서 환경변수를 확인해봅니다.

$ cd direnv
direnv: loading .envrc
direnv: export +HELLO

$ printenv | grep HELLO
HELLO=world

다시 환경변수가 추가된 것을 확인할 수 있습니다.

노트
direnv exec

direnv에서는 특정한 디렉터리로 이동하지 않고도 해당 디렉터리의 환경을 사용할 수 있는 exec <DIR> <COMMAND> 명령어를 제공하고 있습니다.

$ direnv exec ./direnv printenv | grep HELLO
direnv: loading direnv/.envrc
HELLO=world

direnv 디렉터리로 이동하지 않았지만 HELLO 환경변수가 정의되어있는 것을 확인할 수 있습니다.

디렉터리 별로 환경변수를 추가하는 기능은 단순하지만 매우 강력합니다.

예를 들어서 2개의 프로젝트를 동시에 작업하고 있다고 가정해보겠습니다. 두 프로젝트는 아마존 웹 서비스Amazon Web Service를 사용하는데, 서로 다른 AWS 계정의 다른 리전과 연결되어있습니다. 프로젝트 A는 X 계정의 서울 리전을 사용합니다. 프로젝트 B는 Y 계정의 도쿄 리전을 사용합니다. AWS 커맨드라인 인터페이스AWS CLI는 프로필로 다중 계정을 지원하고 있습니다. 하지만 이를 사용하는 경우 모든 명령어에 어떤 프로필을 사용하는지를 --profile 옵션으로 지정해야합니다. direnv를 사용하는 경우 AWS CLI의 credentials 파일을 사용하는 대신 각 디렉터리에 환경변수로 인증 정보를 지정하는 전략을 사용할 수 있습니다.

프로젝트 A의 디렉터리에는 다음과 같이 .envrc 파일을 작성합니다.

export AWS_ACCESS_KEY_ID=ACCOUNT_X_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=ACCOUNT_X_SECRET_KEY
export AWS_DEFAULT_REGION=ap-northeast-2

이제 A 디렉터리로 이동해서 AWS CLI를 사용하는 경우 자동적으로 이 인증 정보를 사용합니다.

프로젝트 B의 경우도 마찬가지입니다. 프로젝트 B 디렉터리 아래에 다음과 같이 .envrc 파일을 작성합니다.

export AWS_ACCESS_KEY_ID=ACCOUNT_Y_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=ACCOUNT_Y_SECRET_KEY
export AWS_DEFAULT_REGION=ap-northeast-1

프로젝트 B 디렉터리에서는 이 인증 정보를 사용할 것입니다.

AWS CLI의 프로필이 정의되어있는 경우 직접 인증 정보를 기록 하는 대신 프로필을 환경 변수로 지정하는 것도 가능합니다.

프로젝트 A의 .envrc를 다음과 같이 작성합니다.

export AWS_PROFILE=ACCOUNT_X

프로젝트 B의 .envrc를 다음과 같이 작성합니다.

export AWS_PROFILE=ACCOUNT_Y

이처럼 direnv를 사용하면 디렉터리(프로젝트) 별 설정 관리가 훨씬 수월해집니다.

Git을 사용하는 경우 .gitignore에 추가

direnv는 프로젝트 단위로 사용하는 경우가 많습니다. 프로젝트 단위의 환경 설정이나 민감한 데이터가 포함될 수 있으므로 기본적으로 .envrc 파일은 저장소에 포함하지 않을 것을 권장합니다. 깃Git을 사용하는 경우 .envrc 파일을 .gitignore 파일에 추가해줍니다. 또한 direnv에서 관습적으로 사용하는 .direnv 디렉터리도 추가해줍니다.

.envrc
.direnv

이 내용을 프로젝트 별로 매번 .gitignore에 추가할 수도 있지만, 프로젝트에 따라서 누락이 될 수도 있기 때문에 시스템 전체에 적용되는 .gitignore 파일 작성을 추천합니다.

$ touch ~/.gitignore_global
$ git config --global core.excludesfile ~/.gitignore_global
$ echo '.envrc' > ~/.gitignore_global
$ echo '.direnv' > ~/.gitignore_global

이제 모든 깃 저장소 디렉터리에에서 기본적으로 .envrc.diernv가 무시될 것입니다.

direnv 활용: 프로그래밍 언어 버전 관리

앞에서는 direnv의 환경변수를 추가하는 등 기본적인 사용법에 대해서 알아보았습니다. direnv는 환경변수를 추가하는 기능 이외에도 셸 스크립트에 기반하고 있기 때문에 얼마든지 확장해서 사용할 수 있습니다. 이 중에서도 자주 사용되는 기법 중 하나가 프로그래밍 언어의 버전 관리입니다. 다수의 프로젝트를 사용하는 경우 같은 프로그래밍 언어라도 여러 버전을 동시에 사용하게 됩니다. 이러한 문제를 해결하기 위해서 각 언어 별로 rbenv, pyenv, NVM, asdf 등 각 언어의 버전을 관리해주는 도구들이 존재합니다. direnv를 사용하면 디렉터리를 기반으로 좀 더 깔끔하게 프로그래밍 언어의 버전 관리가 가능해집니다. 여기서는 루비Ruby, 파이썬Python, 노드.jsNode.js 환경을 구축하는 방법을 소개합니다.

루비Ruby

루비의 경우 direnv와 ruby-install의 조합으로 특별한 로직없이 간단하게 버전 관리를 할 수 있습니다. 여기서는 이 방법을 소개합니다. direnv에서는 이외에도 rbenv와 RVM과의 조합도 지원하고 있습니다.

ruby-install로 루비 버전 관리

ruby-install은 다양한 버전의 루비 설치를 도와주는 도구로 맥OS와 리눅스를 지원합니다. 맥OS를 사용하는 경우 홈브류를 사용해 설치할 수 있습니다.

$ brew install ruby-install

--version 옵션으로 설치되었는지 확인해봅니다.

$ ruby-install --version
ruby-install: 0.6.1

--latest로 루비 각 배포판의 최신 버전을 확인할 수 있습니다.

ruby-install --latest
>>> Downloading latest ruby versions ...
>>> Downloading latest jruby versions ...
>>> Downloading latest rbx versions ...
>>> Downloading latest maglev versions ...
>>> Downloading latest mruby versions ...
Stable ruby versions:
  ruby:
    2.2.10
    2.3.7
    2.4.4
    2.5.1
  jruby:
    1.7.27
    9.2.0.0
  rbx:
    3.107
  maglev:
    1.0.0
  mruby:
    1.4.1

여기서는 ruby 2.5.1을 설치해보겠습니다. ruby-install은 루비 소스 코드를 직접 빌드하기 때문에 설치에 꽤 긴 시간이 걸립니다.

$ ruby-install ruby 2.5.1
>>> Installing ruby 2.5.1 into /Users/username/.rubies/ruby-2.5.1 ...
>>> Installing dependencies for ruby 2.5.1 ...
...
>>> Successfully installed ruby 2.5.1 into /Users/username/.rubies/ruby-2.5.1

설치를 시작하는 메시지에서 유추할 수 있듯이 ~/.rubies/ruby-<VERSION>에 루비가 설치 됩니다. 루비 2.5.1이 설치되었는지 확인해봅니다. 설치 디렉터리 아래의 bin 디렉터리 아래의 ruby를 실행합니다.

$ ~/.rubies/ruby-2.5.1/bin/ruby --version
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]

direnv로 루비 프로젝트 버전 관리

루비 프로젝트 디렉터리로 이동해서(없다면 하나 생성해줍니다 😅) .envrc에 다음 내용을 추가해줍니다.

local ruby_dir=$HOME/.rubies/ruby-2.5.1
load_prefix $ruby_dir
layout ruby

ruby_dir은 루비가 설치된 디렉터리의 경로입니다. load_prefixlayout_ruby는 direnv의 표준 라이브러리에서 제공하는 함수입니다. load_prefix 함수는 매개변수로 주어진 경로 아래의 디렉터리들을 PATH, CPATH, LIBRARY_PATH 등 환경변수에 등록하는 역할을 합니다. layout_ruby는 루비의 젬(라이브러리)을 프로젝트 단위로 관리할 수 있도록 설정해줍니다.

이 내용을 허가allow하고, $PATH를 출력해봅니다.

$ echo $PATH
<CURRENT_DIR>/.direnv/bin:<CURRENT_DIR>/.direnv/ruby/bin:<HOME_DIR>/.rubies/ruby-2.5.1/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin

기본적으로 사용하는 $PATH 이외에 3개의 디렉터리가 추가된 것을 확인할 수 있습니다.

처음에는 $PATH에 설정만 되어있을 뿐 아래의 두 디렉터리는 존재하지 않습니다. 아래의 두 디렉터리들은 젬Gem이나 번들러Bundler를 사용할 때 자동적으로 만들어집니다. 여기서는 Gem을 하나 설치해보겠습니다.

$ gem install kramdown

이 젬은 현재 디렉터리의 .direnv 아래에 설치됩니다. tree 명령어로 구조를 확인해봅니다.

$ tree .direnv -L 3
.direnv
└── ruby
    ├── bin
    │   └── kramdown
    ├── build_info
    ├── cache
    │   └── kramdown-1.17.0.gem
    ├── doc
    │   └── kramdown-1.17.0
    ├── extensions
    ├── gems
    │   └── kramdown-1.17.0
    └── specifications
        └── kramdown-1.17.0.gemspec

특히 주목할만한 점은 .direnv/ruby/bin 아래에 젬의 실행 파일이 설치된다는 점입니다. 앞서 $PATH에서 이 디렉터리가 추가된 것을 확인할 수 있었습니다. whichkramdown을 찾아 보면 이 디렉터리가 나옵니다.

$ which kramdown
<CURRENT_DIR>/.direnv/ruby/bin/kramdown

$ kramdown --version
1.17.0

direnv와 번들러 설정

루비에서 많이 사용되는 의존성 관리자인 번들러Bundler도 루비 젬으로 배포됩니다. direnv로 디렉터리에 프로젝트 환경을 구축하면 bundle 실행 파일이 존재하지 않습니다. 맥OS의 경우 기본적으로 시스템에 루비가 설치되어있습니다. 이 상태에서 whichbundle을 찾아보면 시스템에 설치된 bundle의 경로를 반환합니다.

$ which bundle
/usr/local/bin/bundle

번들러는 $GEM_HOME$BUNDLE_BIN 환경변수를 참조하므로 이 bundle을 그대로 사용하더라도 현재 디렉터리의 .direnv 아래에 정상적으로 젬을 설치합니다. 하지만 이는 시스템 환경에 설치된 번들러를 사용하기 때문에 의도치 않은 문제를 발생시킬 수 있습니다.*

따라서 명시적으로 번들러를 설치하는 것을 추천합니다.

$ gem install bundler
Successfully installed bundler-1.16.3

$ which bundle
<CURRENT_DIR>/.direnv/ruby/bin/bundle

bundle의 경로가 현재 디렉터리 아래로 달라진 것을 확인할 수 있습니다. 이는 현재 $PATH.direnv/ruby/bin의 우선 순위가 /usr/local/bin보다 높기 때문입니다.

이번에는 번들러를 사용해서 패키지를 설치해보겠습니다. 다음과 같이 Gemfile을 작성합니다.

source "https://rubygems.org"

gem "sass"

bundle을 실행해서 sass 젬을 설치합니다.

$ bundle
Fetching gem metadata from https://rubygems.org/............
...
Fetching sass 3.5.7
Installing sass 3.5.7
Bundle complete! 1 Gemfile dependency, 6 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

$ bundle info sass
  * sass (3.5.7)
  ...
      Path: <CURRENT_DIR>/.direnv/ruby/gems/sass-3.5.7

앞서 젬을 사용해 설치한 경우와 마찬가지로 .direnv/ruby/gems/ 아래에 젬이 설치되었습니다. 젬으로 설치하는 경우 .direnv/ruby/bin 디렉터리에만 해당 패키지의 실행파일이 추가되지만 번들러로 설치하는 경우 $BUNDLE_BIN에 해당하는 .direnv/bin에도 같은 파일이 추가적으로 복사됩니다. $BUNDLE_BIN$GEM_HOME보다 우선순위가 높습니다.

$ which sass
<CURRENT_DIR>/.direnv/bin/sass

$BUNDLE_BIN 경로가 출력되는 것을 확인할 수 있습니다. direnv와 번들러를 사용하면 또 한가지 장점이 있습니다. 여러 프로젝트 간에 루비 패키지를 공유하는 경우 현재 프로젝트에 고정된 젬의 실행파일을 사용하기 위해 bundle exec 명령어를 사용해야합니다. 하지만 direnv를 사용하는 경우 위와 같이 $BUNDLE_BIN$PATH의 가장 앞에 등록되어있어서 bundle exec를 사용하지 않더라도 이를 사용한 것과 같은 효과를 볼 수 있습니다. 개념도 훨씬 단순합니다.

use_ruby 함수를 direnv 전역 설정 파일에 등록하기

direnv에서는 각 디렉터리에 작성하는 .envrc 이외에 전역 설정 파일인 ~/.direnvrc를 지원합니다.

앞서 루비 프로젝트 설정을 위해 세 줄짜리 .envrc를 작성했습니다.

local ruby_dir=$HOME/.rubies/ruby-2.5.1
load_prefix $ruby_dir
layout ruby

이 내용을 ~/.direnvrc에 함수로 정의하고 .envrc에서는 이 함수를 사용하는 것이 가능합니다. ~/.direnvrc에 아래 내용을 추가합니다.

use_ruby() {
  local ruby_dir=$HOME/.rubies/ruby-$1
  load_prefix $ruby_dir
  layout ruby
}

이제 .envrc는 다음과 같이 작성합니다.

use ruby 2.5.1

use_ruby 함수는 direnv 환경에서 use ruby와 같이 사용할 수 있습니다. 그리고 인자로는 루비 버전을 넘겨줍니다. 이 내용을 허가하고 루비의 경로를 확인합니다.

$ direnv allow
$ which ruby
~/.rubies/ruby-2.5.1/bin/ruby

루비 2.5.1 버전을 찾는 것을 확인할 수 있습니다. 지금까지 작업에서 유추하실 수 있겠지만 다른 버전의 루비를 사용하는 것도 마찬가지입니다. 예를 들어 ruby-install ruby 2.4.4로 루비 2.4.4 버전을 설치했다고 가정해보겠습니다. 특정 프로젝트 디렉터리에서 2.4.4 버전의 루비를 사용하고 싶다면 아래와 같이 .envrc 파일을 작성해줍니다.

use ruby 2.4.4

이 디럭터리에서 루비를 찾아봅니다.

$ which ruby
~/.rubies/ruby-2.4.4/bin/ruby

설정한대로 루비 2.4.4 버전을 사용하고있습니다.

파이썬Python

파이썬과 direnv을 조합해서 사용하는 경우 pyenvvirtualenv를 사용합니다. pyenv는 파이썬의 버전 관리용으로 사용하고 virtualenv는 프로젝트 환경을 관리하는 용도로 사용합니다.

맥OS의 경우 홈브류를 사용해 pyenv를 설치할 수 있습니다. 여기서는 pyenv를 설치하고 파이썬3 최신 버전을 설치합니다. pyenv는 파이썬을 소스에서 직접 빌드하기 때문에 설치에 시간이 걸립니다.

$ brew install pyenv
$ pyenv install 3.7.0
...
Installed Python-3.7.0 to /Users/nacyot/.pyenv/versions/3.7.0

pyenv는 ~/.pyenv 아래에 버전별로 파이썬을 설치합니다. pyenv 프로그램 자체가 파이썬 버전 관리 역할을 수행합니다. direnv와 함께 사용하는 경우 단순히 파이썬을 버전들을 설치하는 용도로만 사용합니다.

이 버전의 direnv는 내부적으로 virtualenv를 사용해 특정 디렉터리의 파이썬 프로젝트를 셋업합니다. 따라서 사용하고자 하는 파이썬 버전의 pip로 virtualenv를 미리 설치해두어야합니다.

$ ~/.pyenv/versions/3.7.0/bin/pip install virtualenv
Successfully installed virtualenv-16.0.0

다음으로 파이썬 프로젝트를 셋업할 수 있도록 파일에 use_python 함수를 정의합니다.

use_python() {
  local python_root=$HOME/.pyenv/versions/$1
  load_prefix "$python_root"
  layout_python "$python_root/bin/python"
}

파이썬 프로젝트 디렉터리 아래에 .envrc 파일을 만들고 아래 내용을 추가합니다.

use python 3.7.0

이 내용을 허가allow해줍니다. 처음 use python을 로드하면 direnv가 자동적으로 현재 디렉터리의 virutalenv 환경을 구축해줍니다.

$ direnv allow
direnv: loading .envrc
direnv: using python 3.7.0
Running virtualenv with interpreter ~/.pyenv/versions/3.7.0/bin/python
Using base prefix '~/.pyenv/versions/3.7.0'
~/.pyenv/versions/3.7.0/lib/python3.7/site-packages/virtualenv.py:1041: DeprecationWarning: the imp module is deprecated in
 favour of importlib; see the module's documentation for alternative uses
  import imp
New python executable in <CURRENT_DIR>/.direnv/python-3.7.0/bin/python
Installing setuptools, pip, wheel...direnv: ([direnv export zsh]) is taking a while to execute. Use CTRL-C to give up.
done.
direnv: export +CPATH +LD_LIBRARY_PATH +LIBRARY_PATH +MANPATH +PKG_CONFIG_PATH +VIRTUAL_ENV ~PATH

virtualenv는 보통 virutalenvwrapper와 함께 사용되고 이 경우 모든 파이썬 가상 환경(virtualenv)이 ~/.virtualenvs 아래에서 관리됩니다. 이와 달리 direnv를 사용하는 경우 프로젝트 디렉터리의 .direnv 아래에 가상환경이 만들어집니다.

tree를 사용해 .direnv 디렉터리의 구조를 확인해봅니다.

$tree .direnv -L 2
.direnv
└── python-3.7.0
    ├── bin
    ├── include
    ├── lib
    └── pip-selfcheck.json

python이나 pipwhich로 찾아보면 이 디렉터리 아래에 있는 실행 파일을 사용하는 것을 확인할 수 있습니다.

$ which python
<CURRENT_DIR>/.direnv/python-3.7.0/bin/python

$ which pip
<CURRENT_DIR>/.direnv/python-3.7.0/bin/pip

이 디렉터리에서 pip로 파이썬 패키지를 설치하면 마찬가지로 .direnv/python-3.7.0/lib/python3.7/site-packages/ 아래에 설치됩니다.

노드.jsNode.js

노드.js에서는 버전 관리 도구를 NVM을 많이 사용합니다. NVM의 경우 셸스크립트를 기반으로 작성되어있어서 사용하려면 셸에 추가적인 설정이 필요합니다. direnv를 사용하는 경우 버전 관리 기능을 직접 사용하지 않아도 되기 때문에 단순히 노드.js의 특정 버전을 설치하는 용도라면 nodebrew를 추천합니다. 맥OS를 사용하는 경우 홈브류로 설치할 수 있습니다. 추가적으로 소스코드 다운로드를 위한 디렉터리를 생성해줍니다.

$ brew install nodebrew
$ mkdir -p ~/.nodebrew/src

install-binary latest 명령어로 최신 버전을 설치할 수 있습니다.

$ nodebrew install-binary latest
Fetching: https://nodejs.org/dist/v10.7.0/node-v10.7.0-darwin-x64.tar.gz
######################################################################## 100.0%
Installed successfully

10.7.0 버전이 설치되었습니다.

이제 .envrc에 노드를 셋업해봅니다. direnv의 표준 라이브러리에는 use_node가 정의되어있습니다. 따라서 루비나 파이썬과 달리 노드.js의 경우 ~/.direnvrcuse_node 함수를 정의하지 않아도 됩니다. use_node 함수의 경우 노드가 설치된 경로를 NODE_VERSIONS 환경변수에서 찾습니다. 따라서 셸의 설정 파일이나 .envrc에 이 환경변수를 정의해야합니다. 또한 버전 명 앞에 붙는 프리픽스를 NODE_VERSION_PREFIX로 지정할 수 있습니다. 여기서는 이 환경변수들을 .envrc에 정의하겠습니다. layout_node도 추가해줍니다.

export NODE_VERSIONS=$HOME/.nodebrew/node/
export NODE_VERSION_PREFIX=v
use node 10.7.0
layout node

노드.js의 패키지 관리자는 기본적으로 프로젝트 디렉터리 아래의 node_modules를 사용합니다. 따라서 루비나 파이썬과 달리 .direnv 디렉터리를 사용하지 않습니다. direnv를 사용하는 경우 노드의 경로를 지정해주는 것 이외에 특별한 것은 없습니다.

노드 경로가 제대로 설정되었는지 확인해봅니다.

$ which node
/Users/nacyot/.nodebrew/node/v10.7.0/bin/node

$ which npm
/Users/nacyot/.nodebrew/node/v10.7.0/bin/npm

NodeBrew로 설치한 Node를 찾는 것을 확인할 수 있습니다.

다른 언어

direnv 저장소의 위키에서는 go, 얼랭Erlang, 하스켈Haskell, Perl 등 다른 언어들의 설정법도 다루고 있습니다. direnv에서 직접 지원하지 않더라도 use_xxxlayout_xxx는 단순히 셸 함수이므로 직접 만들어서 사용할 수도 있습니다.

그 외의 활용법

위에서 다룬 내용 이외에 direnv를 사용하는 데 도움이 될만한 것들을 몇 가지 정리해보았습니다.

전역 설정 파일 .direnvrc

프로그래밍 언어 설정에서 다뤘듯이 .envrc 파일을 로드할 때 항상 먼저 ~/.direnvrc 파일을 읽습니다. 이 파일에 .envrc에서 공통으로 사용할 헬퍼 함수를 정의하거나 항상 읽어야하는 내용을 추가해둘 수 있습니다.

dotenv 파일 불러오기

direnv에서는 dotenv 형식의 환경변수 설정파일을 읽어올 수 있습니다. 다음 내용을 가진 .env이 있다고 가정해보겠습니다.

HELLO1=hello
HELLO2=world

dotenv 함수로 .env 파일을 읽어올 수 있습니다. 다음 내용을 .envrc에 추가합니다.

dotenv .env

이 내용을 허가하면 .env의 환경변수들이 셸 환경에 추가됩니다.

$ direnv allow
direnv: loading .envrc
direnv: export +HELLO1 +HELLO2

$ printenv | grep HELLO
HELLO1=hello
HELLO2=world

상위 디렉터리의 .envrc 읽어오기

.envrc 파일이 있는 경우 하위 디렉터리에서는 모두 이 내용을 따릅니다. 다음과 같은 .envrc 파일이 있다고 가정합니다.

export HELLO=world

foo 디렉터리를 만들고, 이 디렉터리에 들어가서 HELLO 환경변수가 있는지 확인해봅니다.

$ cd foo
$ printenv | grep HELLO
HELLO=world

이 디렉터리에 .envrc를 생성하면 더 이상 상위 디렉터리의 .envrc를 읽어오지 않습니다.

$ touch .envrc
$ direnv allow
$ printenv | grep HELLO
<출력 결과 없음>

이 파일을 추가하려면 source_up 함수를 사용합니다. foo 디렉터리의 .envrc에서 source_up 함수를 실행합니다.

$ echo 'source_up' > .envrc
$ direnv allow
$ printenv | grep HELLO
HELLO=world

정상적으로 상위 디렉터리의 .envrc 파일을 읽어옵니다.

source_up 이외에도 source_env <DIR> 함수를 사용해 다른 디렉터리의 .env를 읽어올 수 있습니다.

direnv 표준 라이브러리

direnv에는 .envrc에서 사용할 수 있는 표준 라이브러리가 준비되어있습니다. 이 글에서 다룬 layout_node, source_up, dotenv 함수도 여기에 정의되어있습니다. 다른 함수들에 대해서는 공식 저장소의 stdlib.sh 파일을 참고해주세요.

마치며

개발자들은 커맨드 라인 인터페이스를 애용합니다. 그만큼 셸의 환경을 구축하기 위한 다양한 시도들이 있었습니다. 셸 환경의 경우 대표적으로 autoenv, dotenv, direnv 같은 도구들이 있었고, 프로그래밍 언어들의 경우 위에서 다룬 rbenv, RVM, NVM, pyenv, virtualenv 모두 셸 환경을 다루기 위한 도구들이라고 할 수 있습니다. direnv는 디렉터리 별로 셸 환경을 설정할 수 있게 해줌으로서 좀 더 심플하게 이 문제에 접근하고 있습니다. direnv가 만능은 아니더라도 프로젝트 별로 셸 환경을 구축할 수 있도록 도와줍니다. 즐거운 커맨드 라인 라이프에 조금이나마 도움이 되기를 바랍니다. 🎶

더 읽을 거리