2015-01-30

רובי: RVM

RVM הוא כלי בסיסי ונפוץ מאוד ברובי המאפשר לנהל כמה גרסאות רובי שונות על אותה המערכת.

אם תנסו לפתח רובי על מערכת ההפעלה "חלונות" - RVM עשוי להיות האכזבה הראשונה שלכם. כ"כ הרבה מדריכים מניחים שאתם משתמשים ב RVM - אבל RVM לא רץ על חלונות (רק יוניקס / לינוקס / מק). החלופות הקיימות לחלונות (למשל pik) - הן לא ממש טובות.

RVM הוא קיצור של "Ruby enVironment Manager" (כפי שמוסבר באתר), אם כי באותו האתר, בכותרת בגדול, מצוין דווקא הקיצור הישן ("Ruby Version Manager"). צחוקים.

RVM מאפשר:
  • להתקין גרסאות רובי שונות (נקראות Rubies) על אותה מערכת ההפעלה, ללא התנגשות (למשל 1.9.3, 2.1.0, ו 2.1.5).
  • להחליף בכל רגע את גרסת הרובי "הנוכחית" בפקודת shell פשוטה.
  • להצמיד גרסת רובי מסוימת לתיקיה מסוימת במערכת הקבצים (כלומר: לפרויקט רובי שנמצא בתיקיה הזו).
  • לנהל רשימה נפרדת של Gems (נקרא gemset) לכל גרסת רובי או לכל פרויקט.

מה מיוחד ברובי, שכלי כמו RVM הופך ל"הכרחי"? מדוע בג'אווה, למשל, אין כלי דומה?
  • RVM מפשט את ההתקנה של רובי. בג'אווה זה היה פשוט, ואין קושי להתקין כמה גרסאות זו לצד זו.
  • אי-היכולת לנהל רשימה נפרדת של Gems לכל פרויקט היא סוג של "חור" במנגנון ה Gem המקורי. RVM מציג פתרון לבעיה זו אם כי גם Bundler, כלי נפוץ מאוד אחר - גם הוא מציע פתרון לאותה הבעיה.
  • התאימות לאחור של המפרשנים של רובי (יהיו אלו Rubinius, YARV, או JRuby) היא פחות טובה מהמקובל בג'אווה. אם פיתחתם אפליקציה על רובי 2.0.3, כנראה שלא תרצו להריץ אותה על מפרשן של גרסה 2.0.5, ובטח לא על מפרשן של 2.2.0.
    יש פה גם עניין תרבותי, אני חושב: הסבירות שמפתחי ג'אווה יבחרו לעבוד על JDK 1.7 עם patch level מסוים - כי פרויקט אחר שלהם על אותה הגרסה (למרות שיש כבר ג'אווה 8), היא גבוהה יותר מהסיכוי שמפתחי רובי יעשו כן. אנשי רובי נמשכים חזק יותר ל"חדש". לעבוד עם רובי 2.1.3, כשיש רובי 2.2.0 (בטא) כבר בחוץ? חחחחחחח!


פוסט זה שייך לסדרה: רובי (Ruby) למפתחי ג'אווה ותיקים



הדוד בוב (Robert C. Martin), בתחפושת "נער הרובי" שלו...


אמונות מקובלות לגבי RVM - נכון, או לא נכון?



"RVM מתאים בעיקר לפיתוח, ב Production הוא עושה בעיות וכדאי להימנע ממנו".

כנראה שבגרסאות מוקדמות היו ל RVM בעיות תאימות לאחור (עם עצמה). נראה שמאז המצב התייצב. אין לי ניסיון בכדי לתת חוות דעת אישית, אבל כותב הספרייה, וגם אחרים - טוענים ש RVM נועד לפרודקשיין, ועושה זאת ללא בעיות.


RVM עובד רק עם Bash

ובכן, RVM מבצע שינויים בקובץ ה bashrc. בזמן ההתקנה.
הוא עצמו כתוב ב shell script, והוא מניח שמספר כלי shell זמינים לו ב path (למשל awk, sed, tar, git וכו'), וכמו כן הוא מניח על זמינות של כמה פקודות shell (מערכים וכו').

אני עובד עם zsh - והוא עובד לי מצוין. אם אתם עובדים עם shell אחר, כנראה שיהיה עליכם לעשות כמה צעדים ידניים (קונפיגורציות + וידוא זמינות הכלים הנחוצים ל RVM). חפשו בגוגל.

זו הזדמנות טובה לספר שיוצר RVM הצליח לגייס לפני כשנה תקציב שיעזור לו לשכתב את RVM מחדש לשפת רובי. באופן זה הוא אמור להיות יציב יותר, להתפתח מהר יותר, ולהיות זמין בקלות על כל ה Shells ואפילו על חלונות (!).


אסור להפעיל RVM עם Sudo (וללא רשות מרגע ודודלי!)

האיסור רלוונטי להתקנה של RVM עצמו ופעולות gem install - שכאשר משתמשים ב RVM אין לבצע מתוך חשבון עם הרשאות root. לא רק שהפקודה עלולה להיכשל, היא יכול לגרום לנזק להתקנה של ה Gem או של RVM - שיהיה לא-קל להתאושש ממנה.

מקור המגבלה הוא השפה בה RVM כתוב, שהיא shell script. כשעובדים ב shell scripts, כמה משתני סביבה ו paths הם שונים בתור משתמש root - מה שמשבש את ההנהגות של RVM, וכנראה ללא דרך מוצא פשוטה.


"את RVM לא ניתן להסיר! פרמטו את המחשב - בכדי להסיר אותו!!"

יש בזה משהו נכון: ההסרה של RVM היא לא כ"כ פשוטה. גם ההתקנה עלולה להסתבך, לפעמים.
מצד שני, אם תחפשו מעט בגוגל - תוכלו למצוא סדרה של כמה צעדים פשוטים שיסירו את RVM בקלות.

האמונה הזו כנראה נובעת מאנשים שמצאו תשובות שגויות / חלקיות בגוגל, מה שסיבך להם אח"כ את תהליך ההסרה. או אולי, חס וחלילה, מישהו שהתקין את RVM עם sudo, ואז בלי sudo - ואז ניסה להסיר....


RVM הוא העבר, יחי rbenv! ... או chruby! ... ?!

RVM הוא כנראה הכלי הנפוץ בתחומו, מה שעשוי אוטומטית (במיוחד בקהילה כמו זו של רובי) לגרום לכלים האחרים להראות אטרקטיביים מעט יותר. שתי האלטרנטיבות הנפוצות הן rbenv ו chruby.

אם תבחנו את ההבדלים בין RVM ל rbenv, למשל - תראו שהכלים דיי דומים. RVM מכסה קצת יותר קרקע, והיא ממומשת בצורה אחרת: עטיפה פקודת ה cd של ה shell (פקודה מאוד נפוצה) ובמחיר של כמה עשרות ms נוספים לכל הפעלה של הפקודה, בעוד rbenv עוטפת בעזרת "shims" את gem את רובי ועוד כמה קבצים אחרים - מה שמסוכן לא פחות.

To paraphrase Jonathan Jackson, RVM is to Rails as rbenv is to Sinatra. Sinatra is a lightweight framework whereas Rails is much more robust. Sometimes Sinatra just fits, and other times you'd be a fool to not go with Rails.

לא יודע... עבורי הרעיון של ללכת עם האופציה הבסיסית והפשוטה (יהיה זה Sinatra או Lotus) - דווקא קוסם.







אז איך נראית העבודה ב RVM (בקצרה)?



אחרי שהתקנתם את RVM, הנה כמה פעולות שימושיות:

$ rvm install 2.0.0

יתקין את רובי גרסה 2.0.1, וייצור לה gemset בשם default. ניתן לציין גם patch level מדויק.

כדי להתחיל להשתמש בגרסה הזו, פשוט מפעילים את הפקודה:

$ rvm use 2.0.0

האם אנו רוצים לשנות ידנית את גרסת הרובי בכל מעבר בין פרויקטים? ברור שלא!

ניתן ליצור קובץ rvmrc. שיעשה זאת עבורנו. נכנס לתיקיית הפרויקט ונקליד:

$ echo 'rvm use 2.0.0' > .rvmrc

כשנצא מהתיקייה ונחזור אליה - RVM ישאל אותנו אם אנו יצרנו את הקובץ, ואם כן, הוא "יאשר" אותו ויחליף את גרסת הרובי הנוכחית אוטומטית בכל כניסה לתיקיה. את קובץ ה rvmrc. כדאי להפקיד ב git repository שלנו.




האמת, שיש תקן, עליו הסכימו מנהלי הגרסאות השונים של רובי לקבצי קונפיגורציה. בואו נשתמש בו:

$ rm .rvmrc
$ echo '2.0.0' > .ruby-version

יש יתרון בסטנדרטיות לא רק במחשבה על מעבר מהיר (הפעלת שורה אחת פחות?!) לכלי ניהול גרסאות אחר - אלא בעיקר כי מספר כלי צד-שלישי מכבדים את הפורמט המשותף, אך לא מכבדים את קובץ ה rvmrc.
היתרון של קובץ rvmrc. הוא שמדובר בקובץ סקריפט לכל דבר שמופעל בעת כניסה לתיקיה. השורה שהכנסנו למעלה rvm use היא פקודת shell לכל דבר, וניתן להוסיף פקודות נוספות.

אם כבר ציינו כלים, אני עובד עם RubyMine שמכבד דווקא רק את הצורה הארוכה (והתקנית) לתיאור גרסת רובי. נעדכן את הקובץ, כך שרובי-מיין יקשר אוטומטית את הפרויקט לגרסת הרובי הנכונה:

$ echo 'ruby-2.0.0' > .ruby-version

יופי!

עכשיו בואו נרחיב את השימוש ל gemsets. ניצור gemset חדש (בשם "project_name") תחת גרסת הרובי הנוכחית (2.0.1), ונהפוך גם את ה gemset שיצרנו להיות הנוכחי הנוכחי:

$ rvm gemset create project_name
$ rvm use 2.0.0@project_name

הקונבנציה המקובלת היא לקרוא ל gemset כשם ה root folder של הפרויקט. RVM משתמש בפורמט של ruby_version@gemset_name בכדי לתאר צמד. נזכיר ש gemset משוייך לגרסת רובי מסוימת.

מכיוון שאנו רוצים שגם המעבר ל gemset יהיה אוטומטי בכניסה לתיקיה, נגדיר קובץ קונפיגורציה:

$ echo 'project_name' > .ruby-gemset

ייתכן ותתקלו בהמלצה לוותר על קובץ ה ruby-gemset. ולכתוב בקובץ ה ruby-version. את הפורמט של RVM, קרי "ruby-2.0.0@project_name". זה יעבוד לכם עם RVM, אך עלול לא לעבוד עם כלים אחרים (זה לא חלק מהסטנדרט).

אם נקרא ל gem list - נראה רק את ה default gems שמגיעים עם רובי גרסה 2.0.0 (יש כ 14 כאלה).
נוודא, ליתר בטחון, שאנו משתמשים בגרסת הרובי וה gemset שאליו התכוונו, ע״י פקודת

$ rvm info | grep HOME

(הדרך הכי קצרה שאני מכיר)
נתקין gem לדוגמה, ונראה שהוא נוסף ל gemset:



אתם יכולים לקרוא ל rvm use 2.0.0 ואז ל gem list בכדי לראות שה gem שהתקנו לא נמצא ב default gemset.

את ה gem עצמו תוכלו למצוא בתיקיית ה gemset המנוהלת תחת התיקייה rvm/gems./~.
זה, פחות או יותר, הבסיס.

RVM מספק עוד כל מיני יכולות. הנה פקודה נחמדה:

$ rvm all do ruby test.rb

המריצה את קובץ הרובי (יש עוד אפשרויות) בכל הגרסאות המותקנות של רובי במערכת, ומסייעת לבדוק הבדלי תאימות / ביצועים / וואט-אבר, בין גרסאות הרובי השונות.


אלטרנטיבה לשימוש ב gemsets


יש כאלו שמעדיפים להימנע משימוש ב gemset, ולהשאיר את ניהול ה gems ל bundler.
למה? אולי זה עושה יותר סדר בראש, אולי בגלל יכולות של bundler כמו פקודת bundle update שתעדכן את כל ה gems לגרסאות עדכניות - כולל התלויות שלהן. לכו תעשו את זה ידנית בעזרת gem update...[א]

אם אתם משתמשים ב bundler, אז:
  • קובץ ה Gemfile מנהל עבורכם את הגרסאות השונות של ה gems.
  • את התקנת ה gems אתם עושים, כנראה, ע"י עדכון של ה Gemfile והרצת הפקודה bundle install.
RVM נוצר בכדי לפתור "חורים" ב RubyGems, אבל הנה bundler עושה את אותו הדבר בעצמו.

אם אתם בוחרים בגישה זו חשוב לשים לב ש RubyGems מריץ תמיד את הגרסה האחרונה המתוקנת במערכת של ה gem שציינתם.
מצוין לכם ב Gemfile את rake גרסה 10.0 בעוד פרויקט אחר דרש את גרסה 10.1? - שתי הגרסאות מותקנות ובהפעלת הפקודה rake תרוץ הגרסה המאוחרת (10.1) על אף שהגרסה המצוינת ב Gemfile היא 10.0. יותר גרוע: התלויות של rake גם הן תבחרנה בגרסה המאוחרת ביותר שצוינה כתואמת. "סמוך על סחבק" - מה שנקרא.

מה עושים?
פשוט זוכרים להפעיל את rake בצורה:

$ bundle exec rake


שזו בעצם הדרך לעבוד עם bundler, כהגדרה. (אנו פשוט נוטים לשכוח)

לא אוהבים להקליד bundle exec כל הזמן? הפקודה:

$ bundle install --binstubs

תיצור עבורכם תיקיית bin שבה יהיו wrappers לכל הפקודות הרלוונטיות (נקראים binstubs, ריילס משתמשת בהם כברירת מחדל), שרצים ב context של bundle exec.



סיכום


האם זה הגיוני שניהול gems שונים לכל פרויקט דורש צד-שלישי שאינו חלק מהפלטפורמה של השפה?
האם ייתכן שכלי כ"כ נפוץ כתוב בעצם ב shell script (כלומר: 20 אלף שורות של shell script), עם כל המגרעות והבעיות שבדבר? שכל כלי בתחום מציב כמה ״חוסרי נוחות״ שיש להתמודד איתם?

ברוכים הבאים לעולם הרובי! אני בעצמי עוד מתרגל :)

בסופו של דבר, כמה חוסרי נוחות קטנים, לא עצרו את קהילת רובי לייצר פרויקטים גדולים ומרשימים. כשאני אומר זאת, אני מרגיש כמו אמריקאי שמתפלא כיצד חיים בישראל, כשאין <נוחות אמריקאית כלשהי>.

בסופו של דבר, בעת כתיבת הקוד, שזה (בשאיפה) היכן שרוב זמן המתכנת הולך - רובי מציעה הרבה נוחיות שמתכנתי ג׳אווה יכולים רק לחלום עליהן. אז נכון... ה tooling הם פחות מלוטשים.


שיהיה בהצלחה!




---

[א] נראה ש RVM מקנאה ביכולת הזו, והיום היא מציעה יכולת דומה בעזרת הפקודה do:

$ rvm all do gem update -V

אני לא יודע לומר אם פקודה זו גם מעדכנת את ה gems שהם תלויות, בצורה רקורסיבית.


---

מקורות / לינקים רלוונטיים:








תגובה 1: