Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ทำความเข้าใจว่า Rbenv, RubyGems และ Bundler ทำงานร่วมกันอย่างไร

การจัดการการขึ้นต่อกันใน Ruby มักจะเกี่ยวข้องกับการระบุเวอร์ชัน Ruby และ gem ที่โปรเจ็กต์ของเราใช้ จากประสบการณ์ของฉันในการทำงานกับ Ruby การดีบักการพึ่งพาอาศัยกันเป็นหนึ่งในความท้าทายที่ยิ่งใหญ่ที่สุดของฉัน ความล้มเหลวไม่ใช่เรื่องปกติเพราะหลายสิ่งหลายอย่าง "แค่ใช้ได้ผล"; แต่เมื่อมีสิ่งผิดปกติเกิดขึ้น มักจะแก้ไขจุดบกพร่องและแก้ไขได้ยากโดยไม่จำเป็น ในบทความนี้ ฉันจะนำเสนอส่วนที่เกี่ยวข้องกับการจัดการการพึ่งพาใน Ruby ซึ่งจะช่วยในการดีบักปัญหาแปลก ๆ เหล่านี้เมื่อเกิดขึ้น

ทำความเข้าใจว่า Rbenv, RubyGems และ Bundler ทำงานร่วมกันอย่างไร

กำลังโหลดรหัสทับทิม

โดยค่าเริ่มต้น ภาษา Ruby มีวิธีหลักสองวิธีในการโหลดโค้ดที่กำหนดไว้ที่อื่น:load &require .

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

วิธีการโหลดทั้งสองยอมรับทั้งเส้นทางแบบสัมบูรณ์และแบบสัมพัทธ์เป็นอาร์กิวเมนต์ อย่างไรก็ตาม มีสองปัจจัยที่แตกต่าง:

  1. โทรหลายครั้งเพื่อ load จะดำเนินการไฟล์อีกครั้ง ในขณะที่มีการเรียก require . หลายครั้ง จะไม่เรียกใช้ไฟล์อีกครั้ง แต่จะส่งกลับ false .
  2. เรียก load แก้ไขเฉพาะเส้นทางสัมบูรณ์และสัมพัทธ์เท่านั้น โทรไปที่ require ตรวจสอบใน $LOAD_PATH เมื่อเส้นทางไม่ได้รับการแก้ไขเป็นเส้นทางที่แน่นอน

ตัวแปรที่สามคือ require_relative ซึ่งใช้เส้นทางสัมพัทธ์เพื่อต้องการรหัสที่สัมพันธ์กับตำแหน่งของไฟล์ปัจจุบัน แทนที่จะเป็นไดเรกทอรีการทำงานของกระบวนการ Ruby

Rbenv

ตัวจัดการเวอร์ชันเป็นเครื่องมือที่ใช้ในการจัดการและสลับระหว่างเวอร์ชันล่ามของเราได้อย่างง่ายดาย (ในกรณีนี้คือ Ruby) และระบุตำแหน่งเพื่อค้นหาอัญมณีสำหรับโครงการของเรา ตัวจัดการเวอร์ชันเป็นเครื่องมือที่ไม่เชื่อเรื่องภาษาเป็นส่วนใหญ่ และภาษาต่างๆ มีการนำไปใช้งานที่เกี่ยวข้องกัน เช่น Nvm, n สำหรับ Node.js, pyenv สำหรับ Python และ Rbenv, rvm และ chruby สำหรับ Ruby ทีนี้ มาดู rbenv เรามาลองกันไหม

ติดตั้งเวอร์ชัน Ruby

เราใช้คำสั่ง rbenv install เพื่อติดตั้ง 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`

สลับระหว่างเวอร์ชัน

มีหลายวิธีในการระบุวิธีสลับระหว่างเวอร์ชันของ Ruby; อย่างสม่ำเสมอ rbenv ทำสิ่งต่อไปนี้:

  • ตรวจสอบ RBENV_VERSION .
  • ค้นหา .ruby-version ในไดเร็กทอรีของสคริปต์และพาเรนต์จนกว่าจะถึงไดเร็กทอรีราก
  • ค้นหา .ruby-version ไฟล์ใน $PWD และไดเร็กทอรีหลักจนกว่าจะถึงไดเร็กทอรีราก
  • ใช้ไฟล์โกลบอล ~/.rbenv/version .

ลำดับความสำคัญเริ่มจากบนลงล่าง ~/.rbenv/version เป็นทางเลือกสุดท้ายและถือเป็นเวอร์ชันสากล ดูด้านล่าง:

# 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

ชิมและรีแฮช

ต้องเข้าใจแนวคิดทั้งสองนี้อย่างเหมาะสมเพื่อให้สามารถดีบัก rbenv อย่างมีประสิทธิภาพ

Shims เป็นสคริปต์ทุบตีแบบเบาที่มีอยู่ใน PATH . ของคุณ เพื่อสกัดกั้นคำสั่งและกำหนดเส้นทางไปยังเวอร์ชันที่เหมาะสมสำหรับการดำเนินการ ในระดับสูงทุกคำสั่ง (เช่น rspec ) ถูกแปลเป็น rbenv exec rspec . ดูรายละเอียดด้านล่าง

ก่อนอื่น rbenv สร้างชิมสำหรับคำสั่งทั้งหมด (rspec , bundle เป็นต้น) ในเวอร์ชัน Ruby ที่ติดตั้งทั้งหมดเพื่อสกัดกั้นการเรียกไปยัง CLI โดยไม่คำนึงถึงเวอร์ชัน ชิมเหล่านี้สามารถพบได้ที่ ~/.rbenv/shims . ชิมทุกตัวมีสคริปต์ทุบตีเหมือนกันดังที่แสดงด้านล่าง:

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

ต่อไป สคริปต์ด้านบนแปลคร่าวๆ ดังต่อไปนี้:

  • หากชื่อโปรแกรมคือ ruby พร้อมอาร์กิวเมนต์ -e ,
    • แปลเป็น rbenv exec ruby <args>
  • หากชื่อโปรแกรมคือ ruby ด้วยเส้นทางไปยังสคริปต์
    • ตั้งค่า RBENV_DIR ไปที่ไดเร็กทอรีของสคริปต์ สิ่งนี้จะเปิดใช้งาน rbenv เพื่อค้นหา .ruby-version ในไดเรกทอรีของสคริปต์ก่อน $PWD . ถ้าเป็น .ruby-version ระบุไว้ทั้งสองตำแหน่ง rbenv เลือกไดเรกทอรีของสคริปต์
  • ถ้าชื่อโปรแกรมไม่ใช่ Ruby,
    • แปลเป็น rbenv exec <program-name> <args> .

สุดท้าย rbenv exec <command-name> <args> ระบุรุ่นที่ถูกต้องที่จะส่งคำสั่งไปโดยการตรวจสอบ RBENV_VERSION ตัวแปรสภาพแวดล้อม จำไว้ว่า RBENV_VERSION ถูกกำหนดโดยอัลกอริทึมที่กำหนดไว้ข้างต้น

ชิมบน PATH . ของคุณ จะต้องถูกนำหน้า; สิ่งนี้ทำให้มั่นใจได้ว่าพวกเขาจะเป็นจุดติดต่อแรกสำหรับโปรแกรมเรียกทำงาน Ruby ของคุณและสามารถสกัดกั้นได้อย่างถูกต้อง วิธีที่ดีที่สุดที่ฉันพบว่าเข้าใจ PATH . ของคุณ ตั้งค่าและทราบว่าแผ่นชิมของคุณดักจับได้ถูกต้องหรือไม่ ดังนี้:

$ which -a bundle

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

which -a bundle :สิ่งนี้มองผ่าน PATH . ของคุณอย่างไร้เดียงสา และพิมพ์ตามลำดับที่พบ สถานที่ที่ bundle สามารถพบได้ หากมีสิ่งใดพิมพ์มาก่อนสิ่งใดใน ~/.rbenv/shims หมายความว่าแผ่นชิมของคุณไม่ได้ตั้งค่าอย่างถูกต้อง rbenv which bundle จะไม่เปิดเผยสิ่งนี้เนื่องจากคำสั่งทำงานในบริบทของ rbenv ไม่ค้นหา PATH . ของคุณ .

การรีแฮชเป็นกระบวนการสร้างชิม เมื่อคุณเพิ่งติดตั้ง Ruby gem ที่มีโปรแกรมเรียกทำงาน เช่น rspec คุณต้องเรียกใช้ rbenv rehash เพื่อสร้างแผ่นชิมเพื่อให้เรียกใช้ rspec . ในภายหลัง สามารถดักจับโดย rbenv และส่งต่อไปยังรุ่น Ruby ที่เหมาะสม

RubyGems

ถัดไปคือ RubyGems สามารถใช้ได้จากเว็บไซต์ Ruby อย่างเป็นทางการ RubyGems เป็นระบบบรรจุภัณฑ์ Ruby ที่ออกแบบมาเพื่ออำนวยความสะดวกในการสร้าง การแบ่งปัน และติดตั้งไลบรารี ในบางวิธี มันคือระบบบรรจุภัณฑ์แบบกระจายที่คล้ายกับ apt-get แต่มุ่งเป้าไปที่ซอฟต์แวร์ Ruby RubyGems เป็นวิธีการโดยพฤตินัยสำหรับการแบ่งปันอัญมณี ปกติจะติดตั้งไว้ที่ ~/.rbenv/versions/{version-number}/lib/ruby/gems/{minor-version}/ หรือตัวแปรขึ้นอยู่กับตัวจัดการเวอร์ชันที่ใช้ วิธีการเริ่มต้นที่จำเป็นของ Ruby Kernel.require ไม่มีกลไกในการโหลดอัญมณีจากไดเร็กทอรีการติดตั้ง Gems RubyGems แพทช์ลิง Kernel.require ถึง

  • ขั้นแรก ค้นหาอัญมณีใน $LOAD_PATH .
  • หากไม่พบ ให้ค้นหาอัญมณีใน GEMS INSTALLATION DIRECTORY .
    • เมื่อพบแล้ว ให้เพิ่มพาธไปที่ $LOAD_PATH .

ใช้งานได้ "โดยกำเนิด" เพราะ Ruby มาพร้อมกับ RubyGems เป็นค่าเริ่มต้นตั้งแต่เวอร์ชัน 1.9; Ruby เวอร์ชันก่อนหน้าจำเป็นต้องติดตั้ง RubyGems ด้วยตนเอง แม้ว่าจะใช้งานได้โดยกำเนิด แต่สิ่งสำคัญคือต้องทราบความแตกต่างนี้เมื่อทำการดีบั๊ก

เจมคือรหัสที่เกี่ยวข้องจำนวนมากซึ่งใช้ในการแก้ปัญหาเฉพาะ ติดตั้งอัญมณีและรับข้อมูลเกี่ยวกับสภาพแวดล้อมอัญมณีดังนี้:

$ 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 แก้ปัญหานี้อย่างไร? มันลิงแพทช์ Kernel ต้องใช้ระบบที่มี require . ของตัวเอง กระบวนการ. ด้วยสิ่งนี้ เมื่อ require honeybadger ถูกเรียก มันจะค้นหาผ่านโฟลเดอร์ gems สำหรับ honeybadger.rb และเปิดใช้งานอัญมณีเมื่อพบ

ตัวอย่างเช่น require 'honeybadger' สร้างบางสิ่งที่คล้ายกับต่อไปนี้:

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

การเปิดใช้งานอัญมณีนั้นหมายถึงการใส่ลงใน $LOAD_PATH . RubyGems ยังช่วยดาวน์โหลดการขึ้นต่อกันของอัญมณีทั้งหมดก่อนที่จะดาวน์โหลดตัวอัญมณีเอง

นอกจากนี้ Rubygems ยังมาพร้อมกับคุณสมบัติที่ดีที่ช่วยให้คุณเปิดไดเร็กทอรีของ gem ที่เกี่ยวข้องด้วย gem open <gem-name>; ตัวอย่างเช่น

ทำความเข้าใจว่า Rbenv, RubyGems และ Bundler ทำงานร่วมกันอย่างไร

ซึ่งช่วยให้เราค้นหา/ติดตามเวอร์ชันเฉพาะของอัญมณีที่แอปของเรากำลังอ้างอิงได้อย่างง่ายดาย

มัดรวม

ที่เลเยอร์นี้ Bundler ช่วยให้เราระบุการพึ่งพาโปรเจ็กต์ทั้งหมดของเราได้อย่างง่ายดาย และสามารถเลือกระบุเวอร์ชันสำหรับแต่ละโปรเจ็กต์ได้ จากนั้นจะแก้ไขอัญมณีของเรา เช่นเดียวกับการติดตั้งและการพึ่งพา การสร้างแอปพลิเคชันก่อนการรวมกลุ่มมาพร้อมกับความท้าทายมากมาย เช่น:

  • แอปพลิเคชันของเรามีการอ้างอิงจำนวนมาก และการขึ้นต่อกันเหล่านี้มีการขึ้นต่อกันอื่นๆ ที่หลากหลายและเวอร์ชันที่เกี่ยวข้องกัน การติดตั้งอัญมณีผิดเวอร์ชันจะทำให้แอปของเราเสียหายได้ง่าย และการแก้ไขปัญหานี้ทำให้ต้องเสียน้ำตามากมาย
  • นอกจากนี้ สอง(2) ของการขึ้นต่อกันของเราสามารถอ้างอิงถึงการพึ่งพาระดับที่สามเดียวกันได้ การค้นหาความเข้ากันได้เป็นปัญหา และหากมีปัญหา แสดงว่าเป็นปัญหา
  • ในกรณีที่เรามีแอพพลิเคชั่นหลายตัวในเครื่องเดียวกัน มีการพึ่งพาที่หลากหลาย แอพพลิเคชั่นของเราสามารถเข้าถึงอัญมณีใดๆ ที่ติดตั้งบนเครื่อง ซึ่งขัดกับหลักการของสิทธิพิเศษน้อยที่สุด และแสดงแอพพลิเคชั่นของเรากับอัญมณีทั้งหมดที่ติดตั้งบนเครื่อง โดยไม่คำนึงถึง ไม่ว่าพวกเขาจะเป็นอันตราย

Bundler แก้ปัญหาทั้งสามและช่วยให้เราจัดการการพึ่งพาแอปได้อย่างเหมาะสมโดยทำดังนี้

Bundler แก้ไขการพึ่งพาและสร้าง lockfile:

# Gemfile
gem 'httparty'

ถ้าเราเรียกใช้ bundle หรือ bundle install มันจะสร้าง 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

จากด้านบนนี้ Bundler จะสร้างเวอร์ชันของ httparty ที่จะติดตั้ง รวมถึงการพึ่งพาของตัวเองใน Gemfile.lock. ไฟล์นี้เป็นพิมพ์เขียวของการพึ่งพาแอพของเรา และควรตรวจสอบในการควบคุมเวอร์ชัน ช่วยให้มั่นใจได้ว่าการพึ่งพาโปรเจ็กต์ของเราสอดคล้องกันในทุกสภาพแวดล้อม (การพัฒนา การจัดเตรียม หรือการผลิต)

Bundler แก้ไขความเข้ากันได้ระหว่างการอ้างอิง

มันแก้ไขการพึ่งพาสำหรับ httparty โดยการค้นหาเวอร์ชันที่เหมาะสมสำหรับการขึ้นต่อกันและระบุ Bundler ยังพยายามแก้ไขการขึ้นต่อกันระหว่างอัญมณี ตัวอย่างเช่น

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

เนื่องจากอัญมณีสองเม็ดมีการขึ้นต่อกันที่ไม่เข้ากันและไม่สามารถแก้ไขได้โดยอัตโนมัติ

Bundler จำกัดการเข้าถึงอัญมณีที่ติดตั้ง แต่ไม่ได้ระบุไว้ใน Gemfile ของเรา

ใน gemfile ตัวอย่างดังต่อไปนี้

# Gemfile
gem 'httparty'

# irb
require 'rest-client'

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

มันทำให้แน่ใจว่าเฉพาะการอ้างอิงที่ระบุใน Gemfile . ของเรา โครงการของเราสามารถกำหนดได้

Bundle exec

เมื่อคุณเรียกใช้ rspec ในไดเรกทอรีโครงการ มีความเป็นไปได้ที่จะเรียกใช้เวอร์ชันอื่นนอกเหนือจากที่ระบุไว้ใน Gemfile . เนื่องจากเวอร์ชันล่าสุดจะถูกเลือกให้ทำงานเทียบกับเวอร์ชันที่ระบุใน Gemfile . bundle exec rspec รับรอง rspec ถูกเรียกใช้ในบริบทของโครงการนั้น (เช่น อัญมณีที่ระบุใน Gemfile)

มัดถังขยะ

บ่อยครั้ง เราอ่านบทความที่เราเรียกใช้คำสั่งเช่น ./bin/rails; คำสั่งนี้คล้ายกับ bundle exec rails . Binstubs เป็นตัวห่อหุ้มรอบโปรแกรมเรียกทำงานของ Ruby เพื่อให้ง่ายต่อการใช้งาน bundle exec .

ในการสร้างการรัน binstub ให้ใช้ bundle binstubs gem-name . สิ่งนี้จะสร้าง binstub ใน ./bin โฟลเดอร์แต่กำหนดค่าได้ด้วย --path ไดเร็กทอรีถ้าตั้งค่าไว้

ข้อมูลอ้างอิง

หากต้องการเรียนรู้เพิ่มเติม โปรดดูข้อมูลอ้างอิงเหล่านี้:

  • อัญมณีทำงานอย่างไร
  • Rbenv
  • ทับทิมเจมส์
  • มัดรวม