Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Thành Thạo Quản Lý Dependency Ruby: Rbenv, RubyGems, Bundler Tương Tác Như Thế Nào

Quản lý dependency trong Ruby thường chỉ định version Ruby và gem mà project rely on. Theo kinh nghiệm làm việc với Ruby, debug dependencies là một trong những thách thức lớn nhất. Failures không common vì nhiều thứ "just work"; tuy nhiên, khi things go wrong, chúng thường difficult to debug và fix. Trong bài này, tôi sẽ trình bày các pieces involved in dependency management Ruby. Điều này hỗ trợ debug những strange issues khi chúng xảy ra.

Thành Thạo Quản Lý Dependency Ruby: Rbenv, RubyGems, Bundler Tương Tác Như Thế Nào

Ruby Code Loading

Mặc định, Ruby language cung cấp hai major methods load code defined elsewhere: load & require.

load 'json.rb'
require 'json.rb'
require_relative 'json.rb'

Cả hai loading methods chấp nhận absolute và relative paths. Tuy nhiên, hai differentiating factors:

  1. Multiple calls load sẽ re-execute file, multiple calls require sẽ không re-execute; trả về false.
  2. Calls load resolves chỉ absolute và relative paths. Calls require check $LOAD_PATH khi path không resolve absolute path.

Variant thứ ba require_relative, dùng relative paths require code relative to current file’s location thay vì Ruby process’ working directory.

Rbenv

Version manager là tool manage và easily switch giữa versions interpreter (Ruby) và specify location tìm gems cho project. Version managers largely language agnostic, các ngôn ngữ có implementations: Nvm, n cho Node.js, pyenv cho Python, Rbenv, rvm, chruby cho Ruby. Hãy thử rbenv.

Cài Đặt Ruby Version

Dùng command rbenv install cài bất kỳ version Ruby:

# Install ruby 2.6.1
$ rbenv install 2.6.1
Downloading openssl-1.1.1i.tar.gz... -> https://dqw8nmjcqpjn7.cloudfront.net/e8be6a35fe41d10603c3cc635e93289ed00bf34b79671a3a4de64fcee00d5242
Installing openssl-1.1.1i...
Installed openssl-1.1.1i to /home/directory/.rbenv/versions/2.6.1
Downloading ruby-2.6.1.tar.bz2... -> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.1.tar.bz2
Installing ruby-2.6.1...
ruby-build: using readline from homebrew
Installed ruby-2.6.1 to /home/directory/.rbenv/versions/2.6.1
# Check Installation
$ rbenv versions
# Shows all versions installed.
system
2.6.1
# Lookup versions available for installation
$ rbenv install -L
1.8.5-p52
1.8.5-p113
1.8.5-p114
...
2.7.0-rc1
2.7.0-rc2
2.7.0
...
truffleruby+graalvm-20.1.0
truffleruby+graalvm-20.2.0
truffleruby+graalvm-20.3.0
# Full list ~500 versions, scroll nhiều.
# Shortcut dễ dàng tìm version cụ thể với fzf.
$ rbenv install `rbenv install -L | fzf`

Chuyển Đổi Giữa Các Version

Có nhiều cách xác định switch giữa versions Ruby; rbenv làm theo thứ tự:

  • Check RBENV_VERSION.
  • Tìm file .ruby-version trong directory script và parent cho đến root.
  • Tìm file .ruby-version trong $PWD và parent directories cho đến root.
  • Dùng global file ~/.rbenv/version.

Priority từ trên xuống. ~/.rbenv/version fallback cuối cùng, treated as global version. Xem dưới:

# Inside First Project Root
# Select ruby version for project
$ touch .ruby-version && echo "2.7.1" >> .ruby-version
# Verify selected version
$ ruby --version
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin20]
# Result
$ rbenv version
2.7.1 (set by /path/to/current/directory/.ruby-version)
# Result
# Change selected version
$ : >> .ruby-version && echo "2.6.1" >> .ruby-version
# Verify selection change
$ ruby --version
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-darwin20]
# Result
$ rbenv version
2.6.1 (set by /path/to/current/directory/.ruby-version)
# Change selection with RBENV_VERSION while .ruby-version present
$ export RBENV_VERSION=2.5.1
# Verify selection change
# .ruby-version is ignored.
$ ruby --version
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin20]
# Result
$ rbenv version
2.5.1 (set by RBENV_VERSION environment variable)
# Result
# Change to version not installed & remove RBENV_VERSION
$ unset RBENV_VERSION & : >> .ruby-version && echo "2.4.1" >> .ruby-version
# Verify selection change
$ ruby --version
rbenv: version `2.4.1' is not installed (set by full/path/to/current/directory/.ruby-version)
# Result

Shims Và Rehashing

Hai concepts này cần hiểu đúng để debug rbenv hiệu quả.

Shims là lightweight bash scripts tồn tại trong PATH intercept commands và route đến appropriate version để execute. High level, mọi command (ví dụ: rspec) translated thành rbenv exec rspec. Chi tiết dưới.

Thứ nhất, rbenv tạo shim cho all commands (rspec, bundle, etc.) across all installed Ruby versions intercept CLI calls regardless version. Shims tại ~/.rbenv/shims. Mỗi shim chứa same bash script:

#!/usr/bin/env bash
set -e
[ -n "$RBENV_DEBUG" ] && set -x
program="${0##*/}"
if [ "$program" = "ruby" ]; then
  for arg; do
    case "$arg" in
      -e* | -- ) break ;;
      */* )
        if [ -f "$arg" ]; then
          export RBENV_DIR="${arg%/*}"
          break
        fi
        ;;
    esac
  done
fi
export RBENV_ROOT="/home/directory/.rbenv"
exec "/usr/local/Cellar/rbenv/1.1.2/libexec/rbenv" exec "$program" "$@"

Script trên roughly translates to:

  • Nếu program name ruby với argument -e,
    • Translate to rbenv exec ruby <args>
  • Nếu program name ruby với path to script,
    • Set RBENV_DIR to script's directory. Cho phép rbenv tìm .ruby-version trong script's directory trước $PWD. Nếu .ruby-version specified cả hai, rbenv chọn script's directory.
  • Nếu program name không phải Ruby,
    • Translate to rbenv exec <program-name> <args>.

Cuối cùng, rbenv exec <command-name> <args> xác định correct version pass command bằng check RBENV_VERSION environment variable. Nhớ RBENV_VERSION set bởi algorithm trên.

Shims trên PATH phải prepended; đảm bảo chúng first point of contact cho Ruby executables và intercept properly. Cách tốt nhất hiểu PATH setup và biết shims intercept properly:

$ which -a bundle
/path/to/home/.rbenv/shims/bundle
/usr/bin/bundle

which -a bundle: naive look through PATH print out order found, locations bundle có thể tìm thấy. Nếu in ra gì trước ~/.rbenv/shims, shims không setup properly. rbenv which bundle không reveal vì command works in context rbenv không search PATH.

Rehashing là process tạo shims. Khi mới install Ruby gem cung cấp executable, như rspec, cần chạy rbenv rehash tạo shim để subsequent calls rspec intercepted by rbenv và passed on appropriate Ruby version.

RubyGems

Tiếp theo RubyGems. Available từ official Ruby site. RubyGems là Ruby packaging system thiết kế facilitate creation, sharing, installation libraries; cách nào đó giống distribution packaging system như apt-get, nhưng target Ruby software. RubyGems de-facto method sharing gems. Thường cài tại ~/.rbenv/versions/{version-number}/lib/ruby/gems/{minor-version}/, hoặc variant, tùy version manager. Ruby's default required method Kernel.require không provide mechanism load gems từ Gems installation directory. RubyGems monkey-patches Kernel.require để

  • First, search gems trong $LOAD_PATH.
  • Nếu không tìm, search gems trong GEMS INSTALLATION DIRECTORY.
    • Khi tìm thấy, add path vào $LOAD_PATH.

Điều này works "natively" vì Ruby đi kèm RubyGems default từ version 1.9; Ruby versions trước cần cài RubyGems manually. Mặc dù works natively, quan trọng biết difference khi debugging.

Gem là bunch related code solve specific problem. Install gem và get info gem environment:

$ gem install gemname
$ gem env
RubyGems Environment:
- RUBYGEMS VERSION: 3.1.2
- RUBY VERSION: 2.7.1 (2020-03-31 patchlevel 83) [x86_64-darwin20]
- INSTALLATION DIRECTORY: /path/to/home/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0
- USER INSTALLATION DIRECTORY: /path/to/home/.gem/ruby/2.7.0
- RUBY EXECUTABLE: /path/to/home/.rbenv/versions/2.7.1/bin/ruby
- GIT EXECUTABLE: /usr/bin/git
- EXECUTABLE DIRECTORY: /path/to/home/.rbenv/versions/2.7.1/bin
- SPEC CACHE DIRECTORY: /path/to/home/.gem/specs
- SYSTEM CONFIGURATION DIRECTORY: /path/to/home/.rbenv/versions/2.7.1/etc
- RUBYGEMS PLATFORMS:
  - ruby
  - x86_64-darwin-20
- GEM PATHS:
  - /path/to/home/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0
  - /path/to/home/.gem/ruby/2.7.0
- GEM CONFIGURATION: ...
- REMOTE SOURCES:
  - https://rubygems.org/
- SHELL PATH:
  - /path/to/home/.rbenv/versions/2.7.1/bin

RubyGems giải quyết problem như thế nào? Nó monkey patches Kernel's require system với own require method. Với in-place, khi require honeybadger called, search gems folder honeybadger.rb và activate gem khi found.

Ví dụ, require 'honeybadger' produces something similar:

  • spec = Gem::Specification.find_by_path('honeybadger')
  • spec.activate

Activating gem nghĩa là put nó trong $LOAD_PATH. RubyGems cũng giúp download all gem's dependencies trước download gem itself.

Cũng, Rubygems ship với nice feature mở associated gem's directory với gem open <gem-name>; ví dụ,

Thành Thạo Quản Lý Dependency Ruby: Rbenv, RubyGems, Bundler Tương Tác Như Thế Nào

Cho phép dễ dàng find/trace specific version gem app referencing.

Bundler

Ở layer này, Bundler giúp easily specify all project dependencies và optionally specify version cho mỗi. Sau đó, resolve gems, install nó và dependencies. Build real-world apps pre-bundler có myriad challenges:

  • Apps exist với numerous dependencies, dependencies có various other dependencies và versions. Install wrong version one gem dễ break app, fix involved lots of tears.
  • Hai dependencies có thể refer same third-level dependency. Tìm compatibility issue, nếu có thì problem.
  • Multiple apps trên same machine, various dependencies, app access any gems installed on machine, against principle of least privilege và expose app all gems regardless malicious.

Bundler giải quyết cả ba problems và gives sane way manage app dependencies bằng:

Bundler Resolves Dependencies Và Generates Lockfile:

# Gemfile
gem 'httparty'

Chạy bundle hoặc bundle install, generate lockfile:

GEM
  specs:
    httparty (0.18.1)
      mime-types (~> 3.0)
      multi_xml (>= 0.5.2)
    mime-types (3.3.1)
      mime-types-data (~> 3.2015)
    mime-types-data (3.2020.1104)
    multi_xml (0.6.0)
PLATFORMS
  ruby
DEPENDENCIES
  httparty
BUNDLED WITH
  2.1.4

Từ trên, bundler generate version httparty install, và dependencies trong Gemfile.lock. File này blueprint app dependencies, nên check into version control. Đảm bảo project dependencies consistent across environments (development, staging, production).

Bundler Resolves Compatibility Among Dependencies

Resolve dependencies httparty bằng cách tìm suitable version cho dependencies và specify chúng. Bundler cũng thử resolve dependencies giữa gems. Ví dụ,

# Gemfile
gem 'httparty'
# That relies on gem 'mime-types', '>= 3.0.1, < 4.0.1'
gem 'rest-client'
# That relies on gem 'mime-types', '>= 2.0.1, < 3.0'
Bundler could not find compatible versions for gem "mime-types":
In Gemfile:
  httparty was resolved to 0.18.1, which depends on mime-types ('>= 3.0.1, < 4.0.1')
  rest-client was resolved to 2.0.4, which depends on mime-types ('>= 2.0.1, < 3.0')

Vì hai gems có dependencies không compatible và không auto resolve.

Bundler Restricts Access To Gems Installed But Not Specified In Gemfile

Trong sample gemfile như sau,

# Gemfile
gem 'httparty'
# irb
require 'rest-client'
# raises LoadError (cannot load such file -- rest-client)

Đảm bảo chỉ dependencies specified in Gemfile có thể required by project.

Bundle exec

Khi chạy rspec trong project directory, có possibility chạy version khác so với Gemfile. Vì most recent version selected run versus version in Gemfile. bundle exec rspec đảm bảo rspec run in context project (gems specified in Gemfile).

Bundle binstubs

Thường đọc articles chạy commands như ./bin/rails; command này similar bundle exec rails. Binstubs là wrappers around Ruby executables ease usage bundle exec.

Generate binstub chạy bundle binstubs gem-name. Tạo binstub in ./bin folder nhưng configurable with --path directory nếu set.

References

Để learn more, check references:

  • How Do Gems Work?
  • Rbenv
  • RubyGems
  • Bundler