bundle exec은 왜 필요한가?

이 기사는 「어쩐지 모르겠지만 bundle exec

TL;DR



한마디로 bundle exec 를 붙이면 Gemfile.lock 에 쓰여진 대로 requireGemfile.lock 에 기재되어 있는 gem 은, 서로의 의존관계를 채우도록(듯이) 버젼이 선택되어 있으므로, Gem:: ConflictError 를 막을 수가 있습니다.

좀 더 자세히



번들러가 없으면 무슨 일이 일어나는지



구체적인 예에서 설명합니다. 다음과 같은 종속성을 가진 gem, "GemA", "GemB"가 있다고 가정합시다. 둘 다 설치되어 있다고 가정합니다.


gem_a.rb
module GemA
  Version = "1.0.0"
end

gem_b.rb
require 'gem_a'

module GemB
  Version = '1.0.0'

  def self.say
    puts "I'm using GemA ver. #{::GemA::Version}!"
  end
end
# gem list

*** LOCAL GEMS ***

gem_a (1.0.0)
gem_b (1.0.0)

물론 둘 다 require 가능합니다.
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "1.0.0"
irb(main):003:0> require 'gem_b'
=> true
irb(main):004:0> GemB::Version
=> "1.0.0"
irb(main):005:0> GemB::say
I'm using GemA ver. 1.0.0!
=> nil

이제 GemA를 2.0.0으로 업데이트합니다.
# gem update gem_a
Updating installed gems
Updating gem_a
Fetching: gem_a-2.0.0.gem (100%)
Successfully installed  gem_a-2.0.0.gem
Gems updated: gem_a
# gem list

*** LOCAL GEMS ***

gem_a (2.0.0, 1.0.0)
gem_b (1.0.0)

업데이트할 수 있었습니다. irb에서 조금 전과 같은 것을 시도합니다.
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"

제대로 업데이트되었습니다. GemB도 사용해 봅시다.
irb(main):003:0> require 'gem_b'
Traceback (most recent call last):
        8: from /usr/local/bin/irb:11:in `<main>'
        7: from (irb):3
        6: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:39:in `require'
        5: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:128:in `rescue in require'
        4: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems.rb:217:in `try_activate'
        3: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems.rb:224:in `rescue in try_activate'
        2: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/specification.rb:1438:in `activate'
        1: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/specification.rb:2325:in `raise_if_conflicts'
Gem::ConflictError (Unable to activate gem_b-1.0.0, because gem_a-2.0.0 conflicts with gem_a (= 1.0.0))

죄송합니다... 곤란했습니다. GemB가 망가졌습니다. 이미 GemA (2.0.0)를 require 했으므로 GemB에 필요한 GemA (1.0.0)를로드 할 수 없어 오류가 발생했습니다. 1



어떻게 되면 기쁜가?



GemB를 사용하는 프로젝트에서는 업데이트한 GemA(2.0.0)를 사용하지 않고 GemA(1.0.0)를 그대로 사용할 수 있으면 좋았습니다.
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "1.0.0"
irb(main):003:0> require 'gem_b'
=> true
irb(main):004:0> GemB::Version
=> "1.0.0"

한편, GemB를 사용하지 않는 프로젝트에서는 최신 GemA (2.0.0)를 사용하고 싶네요.
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"

이것을 실현하는 것이 Bundler이며, bundle exec 라는 명령입니다.

번들러가 있으면 어떻게 될까



GemA와 GemB를 사용하고 싶은 프로젝트에 이런 느낌의 Gemfile을 준비합니다.
gem 'gem_a'
gem 'gem_b'

그리고 bundle 를 실행하면 Gemfile.lock 가 생성됩니다.
# bundle 
Fetching gem metadata from https://rubygems.org/.
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using bundler 1.16.2
Using gem_a 1.0.0
Using gem_b 1.0.0
Bundle complete! 2 Gemfile dependencies, 3 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

# cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    gem_a (1.0.0)
    gem_b (1.0.0)
      gem_a (= 1.0.0)

PLATFORMS
  ruby

DEPENDENCIES
  gem_a
  gem_b

BUNDLED WITH
   1.16.2

이 상태에서 bundle exec irb 해 봅시다.
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "1.0.0"
irb(main):003:0> require 'gem_b'
=> true
irb(main):004:0> GemB::Version
=> "1.0.0"
irb(main):005:0> GemB::say
I'm using GemA ver. 1.0.0!
=> nil

제대로 움직입니다!

일반적으로 irb를 시작하면 새로운 버전의 GemA (2.0.0)도 제대로 사용할 수 있습니다.
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"
Gemfile 에서 gem_b
gem 'gem_a'
# bundle
Using bundler 1.16.2
Using gem_a 2.0.0
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

# cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    gem_a (2.0.0)

PLATFORMS
  ruby

DEPENDENCIES
  gem_a

BUNDLED WITH
   1.16.2

Bundler는 훌륭합니다.

요약



Bundler가 있다면 다양한 버전의 gem을 많이 설치해도 충돌하지 않도록 bundle 할 수 있습니다.

(여담입니다만, 위와 같은 움직임이므로, 기본적으로는 require


"왜 GemB는 GemA (2.0.0)를 사용하지 않고 GemA (1.0.0)를 읽으려고 할 수 있습니까? 그렇다면 Bundler의 일이 아닙니까?"라고 생각하는 사람은 아마 Gem과 Bundler 의 기능을 혼동합니다. gem 종속성은 gem별로 정의됩니다 (gem 내에 .gemspec이라는 정의 파일이 있습니다). 이 때문에, gem을 1개 사용하는 것만이라면 보통으로 bundle --path=vendor/bundle 하면(자), 의존하고 있는 gem도 함께 적절한 버젼이 로드됩니다. 한편, Bundler는 "여러 개의 gem을 동시에 사용할 때 어떻게 의존관계를 해결하면 require 를 피할 수 있을까?"라는 문제를 해결하기 위한 것입니다.

좋은 웹페이지 즐겨찾기