การจัดการการขึ้นต่อกันใน Ruby มักจะเกี่ยวข้องกับการระบุเวอร์ชัน Ruby และ gem ที่โปรเจ็กต์ของเราใช้ จากประสบการณ์ของฉันในการทำงานกับ Ruby การดีบักการพึ่งพาอาศัยกันเป็นหนึ่งในความท้าทายที่ยิ่งใหญ่ที่สุดของฉัน ความล้มเหลวไม่ใช่เรื่องปกติเพราะหลายสิ่งหลายอย่าง "แค่ใช้ได้ผล"; แต่เมื่อมีสิ่งผิดปกติเกิดขึ้น มักจะแก้ไขจุดบกพร่องและแก้ไขได้ยากโดยไม่จำเป็น ในบทความนี้ ฉันจะนำเสนอส่วนที่เกี่ยวข้องกับการจัดการการพึ่งพาใน Ruby ซึ่งจะช่วยในการดีบักปัญหาแปลก ๆ เหล่านี้เมื่อเกิดขึ้น
กำลังโหลดรหัสทับทิม
โดยค่าเริ่มต้น ภาษา Ruby มีวิธีหลักสองวิธีในการโหลดโค้ดที่กำหนดไว้ที่อื่น:load
&require
.
load 'json.rb'
require 'json.rb'
require_relative 'json.rb'
วิธีการโหลดทั้งสองยอมรับทั้งเส้นทางแบบสัมบูรณ์และแบบสัมพัทธ์เป็นอาร์กิวเมนต์ อย่างไรก็ตาม มีสองปัจจัยที่แตกต่าง:
- โทรหลายครั้งเพื่อ
load
จะดำเนินการไฟล์อีกครั้ง ในขณะที่มีการเรียกrequire
. หลายครั้ง จะไม่เรียกใช้ไฟล์อีกครั้ง แต่จะส่งกลับfalse
. - เรียก
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>
; ตัวอย่างเช่น
ซึ่งช่วยให้เราค้นหา/ติดตามเวอร์ชันเฉพาะของอัญมณีที่แอปของเรากำลังอ้างอิงได้อย่างง่ายดาย
มัดรวม
ที่เลเยอร์นี้ 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
- ทับทิมเจมส์
- มัดรวม