סקירה כללית של Gradle build

בדרך כלל, אפליקציות ל-Android נבנות באמצעות מערכת ה-build של Gradle. לפני שנצלול לפרטים של הגדרת ה-build, נסביר את המושגים שמאחורי ה-build כדי שתוכלו לראות את המערכת כולה.

מה זה build?

מערכת build הופכת את קוד המקור לאפליקציה שניתנת להפעלה. תהליכי build כוללים בדרך כלל כמה כלים לניתוח, הידור, קישור ואריזה של האפליקציה או הספרייה. ‫Gradle משתמש בגישה מבוססת-משימות כדי לארגן ולהריץ את הפקודות האלה.

משימות כוללות פקודות שמתרגמות את הקלט שלהן לפלט. פלאגינים מגדירים משימות ואת ההגדרות שלהן. כשמחילים פלאגין על ה-build, המשימות שלו נרשמות ומקושרות זו לזו באמצעות הקלט והפלט שלהן. לדוגמה, אם תפעילו את הפלאגין של Android Gradle (AGP) בקובץ ה-build, כל המשימות שנדרשות כדי ליצור APK או ספריית Android יירשמו. הפלאגין java-library מאפשר ליצור קובץ JAR מקוד מקור ב-Java. יש פלאגינים דומים ל-Kotlin ולשפות אחרות, אבל פלאגינים אחרים נועדו להרחיב פלאגינים. לדוגמה, הפלאגין protobuf נועד להוסיף תמיכה ב-protobuf לפלאגינים קיימים כמו AGP או java-library.

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

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

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

מה קורה כשמריצים גרסת build של Gradle?

תהליכי build ב-Gradle פועלים בשלושה שלבים. בכל אחד מהשלבים האלה מופעלים חלקים שונים של הקוד שאתם מגדירים בקובצי ה-build.

  • אתחול: קובע אילו פרויקטים ופרויקטי משנה נכללים ב-build, ומגדיר נתיבי classpath שמכילים את קובצי ה-build והפלאגינים שהוחלו. בשלב הזה מתמקדים בקובץ הגדרות שבו מצהירים על הפרויקטים שרוצים לבנות ועל המיקומים שמהם רוצים לאחזר פלאגינים וספריות.
  • ההגדרה רושמת משימות לכל פרויקט, ומריצה את קובץ ה-build כדי להחיל את מפרט ה-build של המשתמש. חשוב להבין שלקוד ההגדרה לא תהיה גישה לנתונים או לקבצים שנוצרו במהלך ההפעלה.
  • הביצוע הוא "הבנייה" בפועל של האפליקציה. הפלט של ההגדרה הוא גרף אציקלי מכוון (DAG) של משימות, שמייצג את כל שלבי ה-build הנדרשים שהמשתמש ביקש (המשימות שסופקו בשורת הפקודה או כברירות מחדל בקובצי ה-build). התרשים הזה מייצג את הקשר בין המשימות, באופן מפורש בהצהרה של המשימה או על סמך הקלט והפלט שלה. אם למשימה יש קלט שהוא פלט של משימה אחרת, היא חייבת לפעול אחרי המשימה האחרת. בשלב הזה, המשימות הלא מעודכנות מופעלות לפי הסדר שמוגדר בתרשים. אם הקלט של משימה מסוימת לא השתנה מאז ההפעלה האחרונה שלה, Gradle ידלג עליה.

מידע נוסף זמין במאמר מחזור החיים של ה-build של Gradle.

שפות DSL להגדרות אישיות

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

שפות DSL מנסות להקל על כולם, מומחי דומיינים ומתכנתים, לתרום לפרויקט, על ידי הגדרת שפה קטנה שמייצגת נתונים בצורה טבעית יותר. פלאגינים של Gradle יכולים להרחיב את ה-DSL כדי להגדיר את הנתונים שהם צריכים למשימות שלהם.

לדוגמה, כך נראית הגדרת החלק של Android ב-build:

Kotlin

android {
    namespace = "com.example.app"
    compileSdk {
        version = release(36) {
            minorApiLevel = 1
        }
    }
    // ...

    defaultConfig {
        applicationId = "com.example.app"
        minSdk {
            version = release(23)
        }
        targetSdk {
            version = release(36)
        }
        // ...
    }
}

מגניב

android {
    namespace = 'com.example.app'
    compileSdk {
        version = release(36) {
            minorApiLevel = 1
        }
    }
    // ...

    defaultConfig {
        applicationId = 'com.example.app'
        minSdk {
            version = release(23)
        }
        targetSdk {
            version = release(36)
        }
        // ...
    }
}

מאחורי הקלעים, קוד ה-DSL דומה לזה:

fun Project.android(configure: ApplicationExtension.() -> Unit) {
    ...
}

interface ApplicationExtension {
    var namespace: String?

    fun compileSdk(configure: CompileSdkSpec.() -> Unit) {
        ...
    }

    val defaultConfig: DefaultConfig

    fun defaultConfig(configure: DefaultConfig.() -> Unit) {
        ...
    }
}

כל בלוק ב-DSL מיוצג על ידי פונקציה שמקבלת למבדה כדי להגדיר אותו, ומאפיין עם אותו שם כדי לגשת אליו. כך הקוד בקובצי ה-build נראה יותר כמו מפרט נתונים.

תלויות חיצוניות

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

רכיבי Maven מזוהים לפי שם הקבוצה (חברה, מפתח וכו'), שם הרכיב (שם הספרייה) והגרסה של הרכיב. בדרך כלל זה מיוצג בתור group:artifact:version.

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

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

בקטע הוספת יחסי תלות ב-build נסביר בפירוט איך לציין יחסי תלות.

וריאנטים של build

כשיוצרים אפליקציה ל-Android, בדרך כלל רוצים ליצור כמה וריאציות. הווריאציות מכילות קוד שונה או שהן נוצרות עם אפשרויות שונות, והן מורכבות מסוגי build ומגרסאות מוצר.

סוגי גרסאות ה-build משנים את אפשרויות ה-build המוצהרות. כברירת מחדל, AGP מגדיר סוגי build של "גרסה" ו "ניפוי באגים", אבל אתם יכולים לשנות אותם ולהוסיף עוד (למשל, ל-staging או לבדיקה פנימית).

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

גרסת build להפצה מבצעת אופטימיזציה של האפליקציה, חותמת עליה באמצעות מפתח ההפצה ומגינה על קובצי האפליקציה המותקנים.

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

‫AGP יוצר וריאציות לכל שילוב של סוג גרסת build וגרסת מוצר. אם לא מגדירים טעמים, הווריאציות נקראות על שם סוגי ה-build. אם מגדירים את שניהם, הווריאנט נקרא <flavor><Buildtype>. לדוגמה, עם סוגי ה-build‏ release ו-debug, והגרסאות demo ו-full, ה-AGP ייצור את הווריאציות:

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

השלבים הבאים

עכשיו, אחרי שהבנתם את המושגים שקשורים ל-build, אתם יכולים לעיין במבנה ה-build של Android בפרויקט שלכם.