asdf 툴 버전 매니저로 프로젝트별 elixir 버전 관리

4 minute read

asdf?

asdf is a tool version manager.

Introduction - asdf-vm.com

툴 버전 매니저인 asdf는 이름만 들어서는 어떤 건지 당최 알 수가 없다. macOS에서 빠꾸 없는 homebrew로 elixir를 설치하면 어떤 일이 벌어질까? 프로젝트 하나만 진행하면 불편함이 없다. 하지만 내가 설치한 elixir와 다른 버전을 사용하는 프로젝트를 clone 해서 작업한다면 어떻게 될까? 버전을 바꿔주는 작업을 해야 한다. 만약 이렇게 작업해야 하는 프로젝트가 여러 개라면? 테스트할 게 많아서 특정 branch에서 elixir 버전을 올려서 테스트 중이라서 branch 별 elixir 버전이 다르다면? asdf는 이런 툴 버전 문제를 해결한다.

asdf 설치 방법

Getting Started 페이지를 참고해서 설치한다. 안타깝게도 Windows는 지원을 안 한다. Windows에서 사용하려면 WSL 같은 걸 끼얹어야 한다.

macOS에서는 다음과 같이 설치한다.

$ brew install coreutils curl git
$ git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.12.0

그 후 asdf 실행 path와 completion을 잡아줘야 한다.

bash를 사용하면 ~/.bashrc 파일에 다음을 추가한다.

. "$HOME/.asdf/asdf.sh"
. "$HOME/.asdf/completions/asdf.bash"

셸(shell)별 설치 방법은 3. Install asdf - Getting Started 페이지를 참고한다.

mix new 다음에는 erlang과 elixir 버전 고정

elixir 최신 버전이 나와서 써보려고 버전을 올렸다. 기존에 잘 돌아가던 프로젝트 컴파일이 실패한다. 아차 제거된 API가 있었네. 지금 고치려니 시간이 없다. 최신 버전을 다시 이전으로 돌린다. 아.. homebrew로 설치했는데, 이거 rollback 어떻게 하지? 웹검색을 한다. 많이 당한 시나리오다.

mix new 프로그램으로 새로운 프로젝트를 만들었다면 사용하는 erlang, elixir 버전을 asdf 프로그램을 사용해 고정한다.

만약 erlang, elixir 플러그인(plugin)을 설치한 적이 없다면 먼저 설치해야 한다. asdf에서 플러그인은 언어 런타임 버전 매니저라고 생각하면 된다. erlang과 elixir 버전을 따로 관리하므로 둘 다 추가한다.

$ asdf plugin add erlang
$ asdf plugin add elixir

플러그인을 추가했다. 이제 설치할 수 있는 버전 조회 및 설치가 가능하다. 이건 머신 당 한 번만 해주면 된다.

$ asdf list-all erlang
$ asdf list-all elixir

설치할 수 있는 버전이 나온다. 여기서 필요한 버전을 설치한다.

$ asdf install erlang 26.0.2
$ asdf install elixir 1.15.2-otp-26

최신 버전을 설치했다. asdf에서 설치란 바이너리를 asdf에서 관리하는 디렉터리에 다운로드 받고 필요하면 컴파일하는 과정이다. 어떻게 사용할 것이라고 정의하기 전에는 사용할 수는 없다는 뜻이다.

$ mkdir asdf-test && cd asdf-test
$ asdf local erlang 26.0.2
$ asdf local elixir 1.15.2-otp-26

asdf-test 디렉터리에서 사용하고 싶은 erlang과 elixir 버전을 설정한다. 이런 버전 정보는 어디에 저장될까?

$ cat .tool-versions
erlang 26.0.2
elixir 1.15.2-otp-26

.tool-versions 파일에 저장된다. 이제 elixir 버전을 확인해 보자.

$ elixir --version
Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Elixir 1.15.2 (compiled with Erlang/OTP 26)

잘 세팅됐다.

지금은 asdf 프로그램 install 명령으로 erlang과 elixir 버전을 설치한 다음 local 명령으로 해당 디렉터리에서 사용할 버전을 정의했다. 이렇게 설치부터 하는 경우가 있을 테고 .tool-versions 파일만 있는 경우 erlang과 elixir 버전을 설치하는 경우도 있을 거다. 예를 들어서 github에서 멋진 프로젝트를 clone해서 빌드를 해 볼 경우 .tool-version 에 정의된 elixir 버전이 내 컴퓨터에는 설치되지 않을 수 있다.

$ asdf install
elixir 1.15.2-otp-26 is already installed
erlang 26.0.2 is already installed

이런 경우엔 install 명령을 사용하면 된다. 만약 설치되지 않았다면 웹에서 다운로드 받아서 설치해 준다.

.tool-versions 파일은 git으로 버전 컨트롤해야 할까? 물론 해야 한다. 그래야 erlang, elixir 모두 프로젝트를 개발할 때, 사용한 버전으로 올바르게 설치할 수 있다.

디폴트로 사용할 elixir 설치

특정 프로젝트에서 사용하는 elixir 버전을 고정할 때, local 옵션을 사용했다. 그렇다면 전역적으로 사용할 옵션은? global 되겠다.

local 에서 한 것처럼 디폴트로 사용할 버전을 설치한다.

$ asdf install erlang 24.3
$ asdf install elixir 1.12.3-otp-24

설치했으니 이제 global 명령으로 디폴트로 사용할 버전을 정의한다.

$ asdf global erlang 24.3
$ asdf global elixir 1.12.3-otp-24

디폴트로는 최신이 아니라 이렇게 보수적으로 사용한다고 해보자. 이 버전 세팅은 어디에 저장될까? $HOME/.tool-versions 파일에 저장된다. 좋은 결정이다. global, local로 개념을 나눈 것도 훌륭하고 .tool-versions 라는 scope에 상관없이 사용할 수 있는 파일 포맷도 만들었다.

$ cat $HOME/.tool-versions
erlang 24.3
elixir 1.12.3-otp-24

디폴트로 사용할 elixir와 특정 프로젝트에 사용할 elixir 버전을 모두 설치했다.

automatically switches runtime versions as you traverse your directories

asdf-vm/asdf - github.com

현재 디렉터리에 따라 런타임 버전을 자동으로 바꿔준다고 하니 테스트를 해보자.

$ cd project/asdf-test/
$ elixir --version
Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Elixir 1.15.2 (compiled with Erlang/OTP 26)

이전에 local 로 elixir 1.15.2 버전을 설치한 디렉터리로 이동해서 버전을 확인하니 설치한 대로 나온다.

이제 .tool-versions 파일이 없는 아무 디렉터리나 가서 디폴트 버전을 사용하는지 확인해 본다.

$ cd /etc
$ elixir --version
Erlang/OTP 24 [erts-12.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]

Elixir 1.12.3 (compiled with Erlang/OTP 24)

갑자기 생각나는 /etc 디렉터리로 이동해서 elixir 버전 확인을 해봤다. 디폴트 버전인 1.12.3 버전을 사용한다.

훌륭하다. 이제 erlang, elixir 버전 걱정은 없겠다. 아~ Windows에서는 asdf 프로그램이 돌아가지 않아서 버전 걱정해야 한다.

scripts to rule them all 스크립트에 적용

scripts to rule them all 패턴을 따르고 있는 스크립트에 적용한다.

#!/bin/sh

# script/bootstrap: Resolve all dependencies that the application requires to
#                   run.
# https://github.com/github/scripts-to-rule-them-all

set -e

cd "$(dirname "$0")/.."

if [ "${USE_GLOBAL_ELIXIR}" = true ]; then
    echo "SKIP: use global elixir instead of asdf elixir"
else
    if ! command -v asdf > /dev/null; then
        echo "FAILED: 'asdf' is not installed"
        exit 1
    fi

    asdf plugin add erlang || true
    asdf plugin add elixir || true
    # install all the tools defined in a '.tool-versions'
    asdf install
fi

mix local.hex --force
mix local.rebar --force
mix deps.get

프로젝트 실행에 필요한 의존성을 설치하는 script/bootstrap 스크립트에서 asdf 프로그램 존재 여부 및 erlang, elixir 버전을 설치하게 했다.

이미 elixir가 깔린 환경에서는 구태여 asdf로 다시 설치할 필요가 없다. USE_GLOBAL_ELIXIR 환경 변수를 세팅하면 글로벌 elixir 프로그램을 그냥 사용하게 했다. 주로 CI에서 이 환경변수를 세팅한다.

링크