Computer >> Máy Tính >  >> Lập trình >> Ruby

Hiểu cách Rbenv, RubyGems và Bundler hoạt động cùng nhau

Quản lý các phụ thuộc trong Ruby thường liên quan đến việc chỉ định các phiên bản Ruby và đá quý mà dự án của chúng tôi dựa vào đó. Theo kinh nghiệm của tôi khi làm việc với Ruby, gỡ lỗi các phụ thuộc là một trong những thách thức lớn nhất của tôi. Thất bại không phổ biến bởi vì nhiều thứ "chỉ hoạt động"; tuy nhiên, khi có sự cố, chúng thường khó gỡ lỗi và sửa chữa một cách không cần thiết. Trong bài viết này, tôi sẽ trình bày các phần liên quan đến quản lý phụ thuộc trong Ruby. Điều này sẽ hỗ trợ gỡ lỗi các vấn đề lạ này khi chúng xảy ra.

Hiểu cách Rbenv, RubyGems và Bundler hoạt động cùng nhau

Đang tải mã Ruby

Theo mặc định, ngôn ngữ Ruby cung cấp hai phương thức chính để tải mã được định nghĩa ở nơi khác:load &require .

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

Cả hai phương thức tải đều chấp nhận cả đường dẫn tuyệt đối và đường dẫn tương đối làm đối số. Tuy nhiên, có hai yếu tố khác biệt:

  1. Nhiều cuộc gọi để load sẽ thực thi lại tệp, trong khi nhiều lệnh gọi đến require sẽ không thực thi lại tệp; thay vào đó, nó sẽ trả về false .
  2. Các cuộc gọi để load chỉ giải quyết các đường dẫn tuyệt đối và tương đối. Cuộc gọi đến require kiểm tra trên $LOAD_PATH khi đường dẫn không phân giải thành đường dẫn tuyệt đối.

Biến thể thứ ba là require_relative , sử dụng các đường dẫn tương đối để yêu cầu mã liên quan đến vị trí của tệp hiện tại hơn là thư mục làm việc của quy trình Ruby.

Rbenv

Trình quản lý phiên bản là một công cụ được sử dụng để quản lý và dễ dàng chuyển đổi giữa các phiên bản của trình thông dịch của chúng tôi (trong trường hợp này là Ruby) và chỉ định vị trí để tìm các viên ngọc tương ứng cho dự án của chúng tôi. Trình quản lý phiên bản phần lớn là các công cụ bất khả tri ngôn ngữ và các ngôn ngữ khác nhau có cách triển khai tương ứng, chẳng hạn như Nvm, n cho Node.js, pyenv cho Python và Rbenv, rvm và chruby cho Ruby. Bây giờ, hãy lấy rbenv cho một vòng quay, chúng ta sẽ?

Cài đặt phiên bản Ruby

Chúng tôi sử dụng lệnh rbenv install để cài đặt bất kỳ phiên bản nào của 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

# The full list above amounts to about 500 versions, scrolling through the entire list is a lot.
# The command below is an easy shortcut to find your specific version with fzf.
$ rbenv install `rbenv install -L | fzf`

Chuyển đổi giữa các phiên bản

Có một số cách để xác định cách chuyển đổi giữa các phiên bản của Ruby; luôn luôn, rbenv thực hiện như sau:

  • Kiểm tra RBENV_VERSION .
  • Tìm kiếm .ruby-version tệp trong thư mục của tập lệnh và tệp cha của nó cho đến khi nó truy cập vào thư mục gốc.
  • Tìm kiếm .ruby-version tệp trong $PWD và các thư mục mẹ của nó cho đến khi nó truy cập vào thư mục gốc.
  • Sử dụng tệp chung ~/.rbenv/version .

Ưu tiên đi từ trên xuống dưới. ~/.rbenv/version là dự phòng cuối cùng và được coi là phiên bản toàn cầu. Xem bên 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 is 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 a version that is 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 khái niệm này cần được hiểu đúng để có thể gỡ lỗi rbenv một cách hiệu quả.

Shims là các tập lệnh bash nhẹ tồn tại trong PATH của bạn để chặn các lệnh và định tuyến chúng đến phiên bản thích hợp để thực thi. Ở cấp độ cao, mọi lệnh (ví dụ:rspec ) được dịch sang rbenv exec rspec . Xem chi tiết bên dưới.

Đầu tiên, rbenv tạo một miếng đệm cho tất cả các lệnh (rspec , bundle , v.v.) trên tất cả các phiên bản Ruby đã cài đặt để chặn các cuộc gọi đến CLI bất kể phiên bản nào. Bạn có thể tìm thấy những miếng chêm này tại ~/.rbenv/shims . Mỗi miếng đệm chứa cùng một tập lệnh bash, như được hiển thị bên dưới:

#!/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" "$@"

Tiếp theo, đoạn mã ở trên tạm dịch như sau:

  • Nếu tên chương trình là ruby với một đối số -e ,
    • Dịch sang rbenv exec ruby <args>
  • Nếu tên chương trình là ruby với một đường dẫn đến một tập lệnh,
    • Đặt RBENV_DIR vào thư mục của script. Điều này cho phép rbenv để tìm kiếm .ruby-version trong thư mục của tập lệnh trước $PWD . Nếu một .ruby-version được chỉ định ở cả hai vị trí, rbenv chọn thư mục của tập lệnh.
  • Nếu tên chương trình không phải là Ruby,
    • Dịch sang rbenv exec <program-name> <args> .

Cuối cùng, rbenv exec <command-name> <args> xác định phiên bản chính xác để chuyển lệnh bằng cách kiểm tra RBENV_VERSION biến môi trường. Hãy nhớ rằng, RBENV_VERSION được đặt bởi thuật toán được xác định ở trên.

Shims trên PATH của bạn phải được viết trước; điều này đảm bảo chúng là điểm liên hệ đầu tiên cho các tệp thực thi Ruby của bạn và có thể chặn đúng cách. Cách tốt nhất tôi tìm thấy để hiểu PATH của bạn thiết lập và biết liệu miếng chêm của bạn có đang chặn đúng cách hay không như sau:

$ which -a bundle

/path/to/home/.rbenv/shims/bundle
/usr/bin/bundle

which -a bundle :cái này nhìn qua PATH của bạn một cách ngây thơ và in ra theo thứ tự tìm thấy, vị trí mà bundle có thể được tìm thấy. Nếu thứ gì đó được in trước bất cứ thứ gì trong ~/.rbenv/shims , điều đó có nghĩa là miếng chêm của bạn không được thiết lập đúng cách. rbenv which bundle sẽ không tiết lộ điều này vì lệnh hoạt động trong ngữ cảnh của rbenv không tìm kiếm PATH của bạn .

Làm lại là quá trình tạo ra miếng chêm. Khi bạn mới cài đặt một viên ngọc Ruby cung cấp tệp thực thi, chẳng hạn như rspec , bạn cần chạy rbenv rehash để tạo miếng đệm để các lệnh gọi tiếp theo tới rspec có thể bị chặn bởi rbenv và chuyển sang phiên bản Ruby thích hợp.

RubyGems

Tiếp theo là RubyGems. Nó có sẵn từ trang Ruby chính thức. RubyGems là một hệ thống đóng gói Ruby được thiết kế để tạo điều kiện thuận lợi cho việc tạo, chia sẻ và cài đặt thư viện; về mặt nào đó, nó là một hệ thống đóng gói phân phối tương tự như apt-get, nhưng được nhắm mục tiêu vào phần mềm Ruby. RubyGems là phương thức de-facto để chia sẻ đá quý. Chúng thường được cài đặt tại ~/.rbenv/versions/{version-number}/lib/ruby/gems/{minor-version}/ hoặc biến thể của nó, tùy thuộc vào trình quản lý phiên bản nào được sử dụng. Phương thức bắt buộc mặc định của Ruby Kernel.require không cung cấp bất kỳ cơ chế nào để tải đá quý từ thư mục cài đặt Đá quý. Các bản vá lỗi của RubyGems khỉ Kernel.require đến

  • Trước tiên, hãy tìm kiếm đá quý trong $LOAD_PATH .
  • Nếu không tìm thấy, hãy tìm kiếm đá quý trong GEMS INSTALLATION DIRECTORY .
    • Sau khi tìm thấy, hãy thêm đường dẫn đến $LOAD_PATH .

Điều này hoạt động "nguyên bản" vì Ruby đã đi kèm với RubyGems theo mặc định kể từ phiên bản 1.9; các phiên bản Ruby trước yêu cầu cài đặt RubyGems theo cách thủ công. Mặc dù điều này hoạt động tự nhiên, điều quan trọng là phải biết sự khác biệt này khi gỡ lỗi.

Đá quý là một loạt các mã liên quan được sử dụng để giải quyết một vấn đề cụ thể. Cài đặt một viên ngọc và nhận thông tin về môi trường đá quý như sau:

$ 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 vấn đề này như thế nào? Nó khỉ vá Kernel của hệ thống yêu cầu với require của riêng nó phương pháp. Với vị trí tại chỗ này, khi require honeybadger được gọi, nó tìm kiếm trong thư mục gems cho honeybadger.rb và kích hoạt đá quý khi được tìm thấy.

Ví dụ:require 'honeybadger' tạo ra một cái gì đó tương tự như sau:

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

Kích hoạt một viên ngọc chỉ đơn giản là đặt nó vào $LOAD_PATH . RubyGems cũng giúp tải xuống tất cả các phụ thuộc của một viên đá quý trước khi tải xuống viên đá quý đó.

Ngoài ra, Rubygems đi kèm với một tính năng tuyệt vời cho phép bạn mở thư mục của viên ngọc được liên kết với gem open <gem-name>; ví dụ,

Hiểu cách Rbenv, RubyGems và Bundler hoạt động cùng nhau

Điều này cho phép chúng tôi dễ dàng tìm / theo dõi phiên bản cụ thể của đá quý mà ứng dụng của chúng tôi đang tham chiếu.

Gói

Tại lớp này, Bundler giúp chúng tôi dễ dàng chỉ định tất cả các phụ thuộc dự án của mình và tùy chọn chỉ định phiên bản cho mỗi phần. Sau đó, nó phân giải các gem của chúng ta, cũng như cài đặt nó và các phụ thuộc của nó. Việc xây dựng trình gói sẵn các ứng dụng trong thế giới thực đi kèm với vô số thách thức, chẳng hạn như sau:

  • Các ứng dụng của chúng tôi tồn tại với nhiều phần phụ thuộc và những phần phụ thuộc này có nhiều phần phụ thuộc khác và các phiên bản tương ứng của chúng. Việc cài đặt sai phiên bản của một viên ngọc sẽ dễ dàng phá vỡ ứng dụng của chúng tôi và việc khắc phục sự cố này đòi hỏi rất nhiều nước mắt.
  • Ngoài ra, hai (2) phần phụ thuộc của chúng tôi có thể tham chiếu đến cùng một phần phụ thuộc cấp ba. Việc tìm kiếm khả năng tương thích là một vấn đề và nếu có thì đó là một vấn đề.
  • Khi chúng tôi có nhiều ứng dụng trên cùng một máy, với các phụ thuộc khác nhau, ứng dụng của chúng tôi có thể truy cập vào bất kỳ viên ngọc nào được cài đặt trên máy, điều này đi ngược lại nguyên tắc ít đặc quyền nhất và để ứng dụng của chúng tôi hiển thị với tất cả các viên đá quý được cài đặt trên máy, bất kể cho dù chúng có độc hại hay không.

Bundler giải quyết tất cả ba vấn đề và cung cấp cho chúng tôi một cách lành mạnh để quản lý sự phụ thuộc vào ứng dụng của chúng tôi bằng cách thực hiện những việc sau.

Bundler giải quyết các phần phụ thuộc và tạo một tệp khóa:

# Gemfile
gem 'httparty'

Nếu chúng tôi chạy bundle hoặc bundle install , nó sẽ tạo ra tệp khóa:

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ừ phần trên, trình gói tạo phiên bản của httparty được cài đặt, cũng như các phụ thuộc của riêng nó trong Gemfile.lock. Tệp này là bản thiết kế các phần phụ thuộc ứng dụng của chúng tôi và nên được kiểm tra trong kiểm soát phiên bản. Nó đảm bảo rằng các phụ thuộc dự án của chúng tôi nhất quán trên các môi trường (phát triển, dàn dựng hoặc sản xuất).

Bundler giải quyết khả năng tương thích giữa các thành phần phụ thuộc

Nó giải quyết các phụ thuộc cho httparty bằng cách tìm một phiên bản phù hợp cho các phụ thuộc của nó và chỉ định chúng. Bundler cũng cố gắng giải quyết sự phụ thuộc giữa các viên ngọc. 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'

Ví dụ trên là tùy ý và sẽ dẫn đến lỗi, chẳng hạn như sau:

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')

Điều này là do hai viên ngọc có các phụ thuộc không tương thích và không thể tự động giải quyết.

Bundler hạn chế quyền truy cập vào các đá quý được cài đặt nhưng không được chỉ định trong Gemfile của chúng tôi

Trong một tệp gem mẫu như sau,

# Gemfile
gem 'httparty'

# irb
require 'rest-client'

# raises
LoadError (cannot load such file -- rest-client)

nó đảm bảo rằng chỉ những phụ thuộc được chỉ định trong Gemfile của chúng tôi có thể được yêu cầu bởi dự án của chúng tôi.

Gói thực thi

Khi bạn chạy rspec trong thư mục dự án, có khả năng chạy một phiên bản khác với phiên bản đã được chỉ định trong Gemfile . Điều này là do phiên bản mới nhất sẽ được chọn để chạy so với phiên bản được chỉ định trong Gemfile . bundle exec rspec đảm bảo rspec được chạy trong ngữ cảnh của dự án đó (tức là các đá quý được chỉ định trong Gemfile).

Gói binstubs

Thông thường, chúng tôi đọc các bài báo trong đó chúng tôi chạy các lệnh như ./bin/rails; lệnh này tương tự như bundle exec rails . Binstubs là các trình bao bọc xung quanh các tệp thực thi Ruby để dễ sử dụng bundle exec .

Để tạo một lần chạy binstub, hãy sử dụng bundle binstubs gem-name . Điều này tạo ra một binstub trong ./bin nhưng có thể được định cấu hình bằng --path nếu được đặt.

Tài liệu tham khảo

Để tìm hiểu thêm, hãy xem các tài liệu tham khảo sau:

  • Đá quý hoạt động như thế nào?
  • Rbenv
  • RubyGems
  • Trình gói