2014-07-12

להבין Maven (הצצה ראשונית)

מייבן (Maven, מלשון "מבין" ביידיש) היא ספריית build ו dependency management לסביבת ה JVM, וג'אווה בפרט.

מייבן איננה חדשה, והיא איננה הטרנד החם בתחום - אך היא עדיין נפוצה מאוד. ע"פ סקר מ 2013 - כ 70% ממפתחי הג'אווה משתמשים בה.

אמנם ל Gradle (ספרייה מתחרה, שהיא הטרנד החם בתחום) יש כמה יתרונות מובנים על פני מייבן (קלות תחזוקה: DSL מול XML + מהירות ריצה) - אבל ייקח עוד זמן עד שהיא תוכל להחליף את מייבן: ל Gradle אין עדיין repositories משלה, וכמות ה plugins הזמינים עבורה - נמוכה בהרבה. היא גם מתבססת על Groovy - שפה עם קהילה לא כ"כ גדולה [א].

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

בואו נתבונן לרגע על מרחב הכלים הקיים.

יש כמה וכמה כלי Build נפוצים:
  • Make של C או ++C ו Unix (שנת 1977)
  • Ant של ג'אווה (שנת 2000)
  • מייבן של ג'אווה (שנת 2002)
  • Rake של רובי (לא יודע ממתי בדיוק)
  • SBT - קיצור של Simple Build Tool, של סקאלה (שנת 2008)
  • Gradle לג'אווה / גרובי (שנת 2012)
  • Grunt ו Gulp לג'ווהסקריפט (חדשות למדי).

ויש גם כמה כלים לניהול תלויות:
  • מייבן - הכוללת גם כלי ניהול תלויות. אולי הראשון מסוגו.
  • Ivy (שנת 2007) - קיימת כתת פרויקט של Ant, אך יש לה זכות קיום עצמאית. משמשת את SBT ואולי גם כלים אחרים.
  • Gradle - התחילה כתלויה ב Ivy, אך פיתחה עם הזמן מערכת ניהול תלויות עצמאית.

מערכות לניהול תלויות הן מערכות דומות מאוד ל package managers, כמו אלו של לינוקס, npm של node.js, או bower של ספריות ג'אווהסקריפט. ההבדל הוא שהן מנהלות source code ולא code ל production (כך שאין צורך לבצע התקנה), ושיש להן אינטגרציה לכלי build: כאשר ב build מסומנת תלות בספרייה שלא קיימת - כלי ה build יוריד אותה בצורה אוטומטית, כך שה build יוכל להסתיים בהצלחה.






מה מייבן מספקת, ובמה היא טובה מ Ant?


קרן Apache מנהלת גם את Ant וגם את Maven. מדוע לנהל 2 ספריות מתחרות? מה מייבן (המאוחרת יותר) מנסה לעשות ש Ant לא עשתה?

המודל של Ant דומה למודל של MAKE: סקריפט המפעיל סדרה של פעולות לבניין התוכנה: העתקת קבצים, קומפילציה, מחיקת קבצים זמניים, וכו'. סקריפט פרוצדורלי / אימפרטיבי.

לסקריפט (קובץ build.xml, במקרה של Ant) יש כמה יעדים (Targets / Goals) שנקראים לרוב משהו כמו: build, clean, jar ו test - כ"א הוא תיאור של סדר הפעולות האטומיות (העתקת קבצים, קומפילציה וכו') הנדרש להשגת מטרה זו. בין ה targets השונים ניתן להגדיר תלות, כך שהפעלת אחד (למשל: אריזה ב jar) - תפעיל גם את התהליך בו היא תלויה (למשל: compile). מנגנון התלויות בין ה targets מאפשר לנו לעשות שימוש חוזר בסקריפט ה compile גם ב targets כמו jar או install.

תכונה שימושית נוספת ב Ant היא הגדרה של משתנים - המאפשרים לשנות במקום אחד פרמטר (כמו path מסוים) - ולהשפיע על כל הסקריפט.

דוגמה ל Ant Task פשוטה

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


מייבן חוסכת עבודה רבה שנדרשת עם Ant. היא מתבססת על ההבחנה שרוב פרויקטי הבילד דומים זה לזה. למשל:
  • יש לקמפל את קבצי ה java לקבצי class ולשים אותם בתיקיה זמנית.
  • מקמפלים ומריצים את הקוד של בדיקות היחידה.
  • במידה והבדיקות עברו בהצלחה - בונים jar או war.
  • מנקים את הקבצים הזמניים שנוצרו.
מדוע לכתוב את ה Script הזה כל פעם מחדש? האם האנושות לא יכולה לחסוך לעצמה את "המצאת גלגל ה build" - בכל פעם מחדש?

מייבן מספקת Archetypes (מעין templates) של פרויקטים נפוצים: פרויקט jar, פרויקט ווב, פרויקט ווב של backbone וכו'. שימוש ב Archetypes חוסכת הן עבודת קידוד והן עבודת תכנון - כיצד להרכיב את פרויקט ה build, באיזה מבנות תיקיות להשתמש וכו'.

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

כל פעולות ה build עצמן (יצירת תיקיות, העתקת קבצים, הפעלת קומפיילר וכו') מגיעות כ Plugins - וניתנים להחלפה. מייבן מספקת את המודל, קובץ הקונפיגורציה של מייבן מתאר את ה Strategy (ה design pattern), וה plugins עושים את העבודה.

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

מרגע שמבנה הפרויקט ידוע וקבוע - ניתן לספק הוראות הרבה יותר high level מאשר ב Ant: "בצע קומפילציה" במקום "צור תיקיה זמנית x", "הפעל את javac עם target של x", "העבר קבצים מתיקיה x לתיקיה y", וכו'.



פרויקט פשוט במייבן


יצירת פרויקט של מייבן מתוך Archetype (להזכיר: סוג של template של פרוייקט) יכולה להיעשות בעזרת command line, אבל בפועל כיום יש אינטגרציה לכל IDE נפוץ - כך שאין כ"כ צורך להכיר את ה command line. הבה נבחן את מבנה התיקיות של הפרויקט הגנרי של מייבן:


כמה הסברים:
  • תיקיית src נועדה לקוד, ותיקיית target לתוצרי קומפילציה (קבצי class., למשל). 
  • תיקיית ה src/main נועדה לקוד שמיועד לדילוור, ותחתיה יש תיקיה לכל שפת תכנות. יכולה להיות, למשל, תיקיה ל java, ל scala ול javaScript. ספריית ה src/test נועדה לקוד של בדיקות יחידה / אינטגרציה. בפנים יש תיקיות של ה java packages - כמו בכל פרויקט java רגיל.
  • תיקיית src/main/resources/ נועדה לקבצים אחרים של הפרויקט שאינם קוד, למשל קבצי הקונפיגורציה של Spring Framework.
  • את תיקיית ה target מחלקים לתוצרים של קוד שהולך לדילוור (classes) ותיקיית הקוד שלא הולך לדלוור (test-classes).
  • pom.xml הוא קובץ הקונפיגורציה של מייבן, המקבילה של build.xml של Ant.

קובץ ה pom.xml (קיצור של Project Object Model) הוא קובץ XML, דקלרטיבי, שמגדיר את ה Strategy של תהליך ה build שלנו.

הנה דוגמה ל pom.xml מינימלי ביותר:


הסברים:
  1. כל פרויקט מייבן מוגדר באופן ייחודי ע"י שלושה פרמטרים:
    1. groupId - סוג של namespace שאמור להיות globally unique. בד"כ כתובת אתר האינטרנט של החברה שלכם בסדר הפוך של tokens.
    2. artifactId - שם ייחודי, פנימי אצלכם, לפרויקט.
    3. מספר גרסה, בפורמט: <major ver>.<minor ver>.<incremental ver>-<qualifier>, כאשר ה qualifier הוא טקסט [ב].
      ל qualifier בשם "SNAPSHOT" (אותיות גדולות) - יש משמעות מיוחדת, והיא משמשת לציין גרסה שעדיין בפיתוח שמשתנה כל הזמן. במקום לבדוק אם הגרסה התעדכנה בעזרת מספר הגרסה, הוא יבדוק את התאריך, כך שהמפתח לא נדרש לשנות כל רגע מספר גרסה בכדי שחבריו יקבלו גרסה עדכנית בכל build.
  2. הגדרה של תלות בספריה JUnit. כ default קיבלנו את גרסה 3.8.1 - אך אנו יכולים להחליט שאנו רוצים לעבוד עם JUnit 4. שימו לב שגם ספרייה זו מזוהה בעזרת השילוש הקדוש: קבוצה, artifact וגרסה. זהו הזיהוי הייחודי ב repositories של מייבן.
  3. זוהי תצוגת ה IDE למייבן של IntelliJ - בכל IDE יהיה משהו שנראה אחרת. אנו רואים את השלבים המרכזיים בתהליך הבילד כפי שהוגדר.
שנייה! לא הגדרנו כמעט כלום בקובץ ה pom.xml שלנו. מאיפה מגיעות ההגדרות הללו?
אם אנסה להריץ אחד השלבים, למשל test, מייבן יוריד לי ערמה של maven plugins הנדרשים להרצה - ויריץ קומפילציה ובדיקות בהצלחה. כיצד הוא יודע מה לעשות?

פרויקטים של מייבן מוגדרים בדלתאות (deltas): ההגדרה הבסיסית נמצאת ב super pom.xml - קובץ שנמצא בתוך אחד ה jars של מייבן עצמו (ואפשר, אך לא כדאי, לשנות אותו). הגדרות נוספות מגיעות מה settings של מייבן או מחושבות בזמן ריצה (כמו "תיקיה נוכחית"), אח"כ מ pom.xml אבות של ה pom.xml המדובר (בפרויקט גדול - מרכיבים קבצי pom.xml בהיררכיה), ואז לבסוף, מה pom.xml הנוכחי - שבמקרה שלנו הוא כמעט ריק.

במקרה שלנו אין pom.xml אב, והקובץ שלנו כמעט ריק - אז ההתנהגות בה אנו חוזים מגיע בעיקר מה super pom.xml ומה settings. הרכבה של כל השכבות המוגדרות בקובצי ה pom.xml הרלוונטיים השונים נקראת effective pom.xml, ניתן לראות  אותה ב eclipse בתוך ה IDE. ב Intellij אני לא מכיר דרך לראות אותה, ולכן אני מריץ את ה command line הבא:

mvn help:effective-pom -Doutput=effective-pom.xml


דוגמה ל Effective-pom.xml מינימליסטי


הנה ה effective-pom.xml שנוצר לנו. בואו נבחן אותו. שימו לב שכמה חלקים הם collapsed:


  1. ה dependencies היחידים הם אלו שהגיעו מה pom.xml שלנו - תלות ב Junit 3.8.1
  2. ה repositories וה pluginRepositories כרגע הם ה central repository של מייבן, קרי http://repo.maven.apache.org/maven2.
    ה repositories מכילים הרבה (מאוד) פרויקטי open source ו/או maven plugins - אותם מייבן ידע להוריד אלינו למחשב ע"פ הצורך. כאשר עובדים ב IDE וזקוקים לאיזו ספרייה - פשוט מוסיפים אותה כתלות ב pom.xml ומייבן יביא אותה לבד ב build הבא. אם הספרייה שציינתם תלויה בספריות אחרות - מייבן תביא גם אותן. כמו כן - אותה הורדה תתרחש גם אצל מפתחים אחרים בצוות. זה היופי של ניהול התלויות של מייבן.
  3. כאן ניתן לראות את מבנה הספריות של הפרויקט, כפי שתיארנו אותו קודם (כ full paths). מייבן משתמש ב super pom.xml במשתני-סביבה בכדי להגדיר את הנתיבים, וה effective-pom.xml כבר מכניס בהם את הערכים.
  4. כפי שציינו, plugins הם אלו שעושים את כל עבודת ה build בפועל. ניתן לראות בדוגמה למעלה שני core plugins שמתארים את ההתנהגות של שלבי ה clean וה install של מייבן.
    Plugins אחרים שלא נכנסו לצילום המסך הם:
    1. maven-resources-plugin
    2. maven-surefire-plugin - הפלאג-אין של מייבן להרצת בדיקות-יחידה. אין לי מושג למה הוא קיבל "שם מגניב", ורבים אחרים - לא.
    3. maven-compiler-plugin
    4. maven-jar-plugin - כפי שהשם מצביע, הוא פשוט אורז קובץ jar.
    5. maven-deploy-plugin
    6. maven-site-plugin - הפלאג-אין של מייבן ליצירת תיעוד לפרויקט

בהמשך, נרחיב עוד על Plugins והקונפיגורציה שלהם.



Build Lifecycles


המודל של מייבן מגדיר 3 פעולות שמייבן יודע לעשות:
  • לבנות תוכנה (ואולי גם להתקין אותה) - מה שנקרא ה default lifecycle
  • לנקות שיירים (קבצים זמניים וכו') מבנייה קודמת (בעקרון: ספריית ה target) - מה שנקרא clean lifecycle
  • בניית תיעוד לתוכנה (יצירת מערכת דפי html) - מה שנקרא site lifecycle, כלומר אתר אינטרנט (סטטי) הכולל את התיעוד של התוכנה.
כל אחד ממחזורים אלו בנוי מרשימה מוגדרת-מראש של שלבים (phases).
המשתמש יכול לבחור לבצע רק חלק מהמחזור שהוגדר ע"י מייבן. למשל: להפעיל את ה default lifecycle רק עד שלב הקומפילציה. שלבים מוקדמים יותר במחזור, כגון validation של פרויקט המייבן או יצירת ה resources הנדרשים - יופעלו, אבל השלבים המאוחרים יותר (כמו בדיקות או התקנה) - לא יופעלו.

שלבי ה Lifecycle השונים של מייבן, כאשר השלבים החשובים / היותר שימושיים - מוגדשים


כאשר אנו מפעילים את מייבן בכדי לנקות שיירים של בנייה קודמת, אנו מקלידים ב command line:

mvn clean

מה שיגרום למייבן לזהות שזהו שלב (phase) של ה clean lifecycle ולהפעיל את ה livecycle הזה עד שלב ה clean. שלב ה pre-clean יתבצע, אך שלב ה post-clean - לא יתבצע. כמובן ששלבים רבים במחזורי הפעילות של מייבן הם שלבים ריקים - אלא אם נגדיר בהם משהו.

בכדי להפעיל את כל ה clean lifecycle יש להקליד mvn post-clean. בד"כ אנו מסתפקים ב clean, הקצר יותר להקלדה.

האם איי פעם תהיתם מדוע אתם מקלידים (הפקודה הכי נפוצה אולי) "mvn clean install",
אבל לא "maven clean compile install", או משהו דומה?

התשובה עכשיו ברורה: clean נדרש מכיוון שהוא שלב ב lifecycle שונה מה default lifecycle. כאשר מפעילים את mvn install - הוא יבצע את כל 20 ומשהו השלבים מ validate ועוד install. הוא רק לא יעשה deploy.

הנה כמה מלים על מה שמייבן עושה בשלבים השונים של ה default lifecycle:

  • validate - מוודא שפרויקט המייבן תקין, למשל: ולידציה של ה pom.xml, שכל המשתנים שבשימוש - מוגדרים, וכו'.
  • generate sources / resources - שלבים שהוגדרו בכדי לשמש לשלבי pre-proccesing להתרחש (במידה ואתם משתמשים בכלים שמג'נרטים קוד או resources).
  • compile - קומפילציה של קוד תחת ספריית main (לא כולל קוד של בדיקות)
  • process-classes - שלבי post processing על קבצי ה class. שקומפלו, למשל "אריגה" של AspectJ על קבצים שכבר קומפלו (יש אפשרות כזו).
  • test-compile - מקמפל רק את קבצי הבדיקות. אם אתם לא מתכוונים להריץ בדיקות - חבל על הזמן לקמפל את קוד הבדיקות, לא?
  • package - אריזת הקוד ל jar, war, ear וכו'.
  • integration-tests - שלב מיוחד של הרצת בדיקות על מערכת "חיה". מתקין את ה deployable שכרגע נארז על מערכת בדיקות, ומריץ מולה בדיקות אינטגרציה / מערכת (איך שאתם קוראים להן). ה plugin של מייבן שמריץ בדיקות אינטגרציה נקרא "failsafe".
  • verify - שלב בו ניתן לבצע בדיקות נוספות על ה package הארוז - לוודא שהוא תקין.
  • install - השם של השלב הזה הוא מעט מבלבל: ההתקנה היא של ה deployable ל local maven repository - ולא לשרת ה production כמו שאולי זה נשמע. ה local repository הוא צד של מייבן שעדיין לא נגענו בו בפוסט זה. נאמר כרגע שזו איזה תיקייה של מייבן בה הוא שומר deployables, pugins ו ספריות שנדרשות בגלל התלויות.
  • deploy - עושה את מה שאפשר לחשוב: מתקין את ה deployable על שרתי ה production.



סיכום


בפוסט זה סקרנו מה מייבן עושה, כיצד הוא עושה זאת אחרת מ Ant, וסקרנו כמה מהמנגנונים הבסיסיים שלו - מודל ה lifecycles. עדיין חסרים לנו כמה פרטים חשובים:
  • מהם בדיוק ה repositories?
  • כיצד ה plugins משתמשים בשלבים השונים שמייבן מגדיר? מהם ה goals?
  • ואולי הכי פרקטי: כיצד משנים את הגדרות ה pom.xml ורותמים את מייבן לצרכים הייחודיים של הפרויקט שלנו ?
אני מקווה לכסות לפחות כמה מנושאים אלו בפוסט המשך.


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


---

[א] Groovy היא (מלבד ה nested classes) בעצם superset של ג'אווה. לכאורה, מאוד קל למתכת ג'אווה לעבור אליה: לשנות את סיומות הקבצים ל groovy. ולהימנע משימוש ב nested classes. בפועל, כמעט כל דוגמאות הקוד של gradle משתמשים בתחביר מתקדם של שפת Groovy - שיהיה זר ומוזר למתכנת ג'אווה שלא ישקיע זמן ללמוד אותו ואת הדקויות שלו.

[ב] בגלל שמייבן מתייחס ל qualifier כטקסט, יש פה pitfall מסוים:
גירסה:
0.9.9-CR10 
תחשב כמוקדמת יותר מגירסה:
0.9.9-CR2
(בגלל שהתו "1" נמוך בערך ה ASCII שלו מ "2").



אין תגובות:

הוסף רשומת תגובה