מדריך לסגנון הקוד

סגנון הקוד של HIDL דומה לקוד C++‏ ב-Android framework, עם הכנסות של 4 רווחים ושמות קבצים עם אותיות רישיות קטנות וגדולות. ההצהרות על חבילות, הייבוא והתיאורים של docstrings דומים לאלה ב-Java, עם שינויים קלים.

הדוגמאות הבאות ל-IFoo.hal ול-types.hal מדגימות סגנונות של קוד HIDL ומספקות קישורים מהירים לפרטים על כל סגנון (הוסרו הדוגמאות ל-IFooClientCallback.hal, ל-IBar.hal ול-IBaz.hal).

hardware/interfaces/foo/1.0/IFoo.hal
/*
 * (License Notice)
 */

package android.hardware.foo@1.0;

import android.hardware.bar@1.0::IBar;

import IBaz;
import IFooClientCallback;

/**
 * IFoo is an interface that*/
interface IFoo {

    /**
     * This is a multiline docstring.
     *
     * @return result 0 if successful, nonzero otherwise.
     */
     foo() generates (FooStatus result);

    /**
     * Restart controller by power cycle.
     *
     * @param bar callback interface that* @return result 0 if successful, nonzero otherwise.
     */
    powerCycle(IBar bar) generates (FooStatus result);

    /** Single line docstring. */
    baz();


    /**
     * The bar function.
     *
     * @param clientCallback callback after function is called
     * @param baz related baz object
     * @param data input data blob
     */
    bar(IFooClientCallback clientCallback,
        IBaz baz,
        FooData data);

};
hardware/interfaces/foo/1.0/types.hal
/*
 * (License Notice)
 */

package android.hardware.foo@1.0;

/** Replied status. */
enum Status : int32_t {
    OK,
    /* invalid arguments */
    ERR_ARG,
    /* note, no transport related errors */
    ERR_UNKNOWN = -1,
};

struct ArgData {
    int32_t[20]  someArray;
    vec<uint8_t> data;
};

מוסכמות למתן שמות

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

מבנה הספריות ומתן שמות לקבצים

מבנה הספרייה אמור להיראות כך:

  • ROOT-DIRECTORY
    • MODULE
      • SUBMODULE (אופציונלי, יכולות להיות כמה רמות)
        • VERSION
          • Android.mk
          • IINTERFACE_1.hal
          • IINTERFACE_2.hal
          • IINTERFACE_N.hal
          • types.hal (אופציונלי)

איפה:

  • ROOT-DIRECTORY הוא:
    • hardware/interfaces לחבילות הליבה של HIDL.
    • vendor/VENDOR/interfaces לחבילות של ספקים, כאשר VENDOR מתייחס לספק SoC או ליצרן ציוד מקורי (OEM)/ליצרן ציוד בהתאמה אישית (ODM).
  • MODULE צריכה להיות מילה אחת באותיות קטנות שמתארת את מערכת המשנה (לדוגמה, nfc). אם צריך יותר ממילה אחת, צריך להשתמש ב-SUBMODULE בתצוגת עץ. יכולות להיות כמה רמות של עיטורים.
  • השדה VERSION צריך להיות באותה גרסה בדיוק (major.minor) כפי שמתואר בקטע גרסאות.
  • IINTERFACE_X צריך להיות שם הממשק עם UpperCamelCase/PascalCase (לדוגמה, INfc) כפי שמתואר בקטע שמות ממשק.

דוגמה:

  • hardware/interfaces
    • nfc
      • 1.0
        • Android.mk
        • INfc.hal
        • INfcClientCallback.hal
        • types.hal

הערה: לכל הקבצים צריכות להיות הרשאות שלא מאפשרות להריץ אותם (ב-Git).

שמות חבילות

שמות החבילות חייבים להיות בפורמט השם המוגדר במלואו (FQN) הבא (שנקרא PACKAGE-NAME):

PACKAGE.MODULE[.SUBMODULE[.SUBMODULE[]]]@VERSION

איפה:

  • PACKAGE היא החבילה שממופה ל-ROOT-DIRECTORY. בפרט, הערך של PACKAGE הוא:
    • android.hardware לחבילות הליבה של HIDL (מיפוי ל-hardware/interfaces).
    • vendor.VENDOR.hardware לחבילות של ספקים, כאשר VENDOR מתייחס לספק SoC או ל-OEM/ODM (מיפוי ל-vendor/VENDOR/interfaces).
  • MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION הם אותם שמות תיקיות בדיוק במבנה שמתואר בקטע מבנה הספרייה.
  • שמות החבילות צריכים להיות באותיות קטנות. אם השם כולל יותר ממילה אחת, צריך להשתמש במילים כמודולים משניים או לכתוב אותן ב-snake_case.
  • לא מוסיפים רווחים לכתובת.

השם המלא תמיד מופיע בהצהרות על חבילות.

גרסאות

הגרסאות צריכות להיות בפורמט הבא:

MAJOR.MINOR

הגרסה של MAJOR ושל MINOR צריכה להיות מספר שלם אחד. ב-HIDL נעשה שימוש בכללים של ניהול גרסאות סמנטי.

ייבוא

לייבא יש אחד משלושת הפורמטים הבאים:

  • ייבוא של חבילות שלמות: import PACKAGE-NAME;
  • ייבוא חלקי: import PACKAGE-NAME::UDT; (או, אם הסוג שיובא נמצא באותה החבילה,import UDT;
  • ייבוא של סוגים בלבד: import PACKAGE-NAME::types;

השדה PACKAGE-NAME צריך להיות בפורמט שמתואר בקטע שמות חבילות. קובץ types.hal של החבילה הנוכחית (אם הוא קיים) מיובא באופן אוטומטי (אין לייבא אותו באופן מפורש).

שמות שמוגדרים במלואם (FQN)

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

android.hardware.nfc@1.0::INfcClientCallback

בקובץ אחר בקטע android.hardware.nfc@1.0, קוראים לממשק שלמעלה בשם INfcClientCallback. אחרת, צריך להשתמש רק בשם המלא.

קיבוץ והזמנת ייבוא

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

  1. חבילות android.hardware אחרות (יש להשתמש בשמות מוגדרים במלואם).
  2. חבילות vendor.VENDOR אחרות (יש להשתמש בשמות מוגדרים במלואם).
    • כל ספק צריך להיות קבוצה.
    • מיון הספקים לפי סדר אלפביתי.
  3. ייבוא מממשקי API אחרים באותה חבילה (שימוש בשמות פשוטים).

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

import android.hardware.nfc@1.0::INfc;
import android.hardware.nfc@1.0::INfcClientCallback;

/* Importing the whole module. */
import vendor.barvendor.bar@3.1;

import vendor.foovendor.foo@2.2::IFooBar;
import vendor.foovendor.foo@2.2::IFooFoo;

import IBar;
import IFoo;

שמות ממשקים

שמות ממשקים חייבים להתחיל ב-I, ואחריהם שם UpperCamelCase/PascalCase. צריך להגדיר ממשק בשם IFoo בקובץ IFoo.hal. הקובץ הזה יכול להכיל הגדרות רק לממשק IFoo (הממשק INAME צריך להיות ב-INAME.hal).

פונקציות

בשמות של פונקציות, ארגומנטים ומשתני החזרה, משתמשים ב-lowerCamelCase. דוגמה:

open(INfcClientCallback clientCallback) generates (int32_t retVal);
oneway pingAlive(IFooCallback cb);

שמות של שדות של מבנים ושל איחודים

בשמות של שדות של מבנה או של איחוד, משתמשים ב-lowerCamelCase. דוגמה:

struct FooReply {
    vec<uint8_t> replyData;
}

שמות של סוגים

שמות הסוגים מתייחסים להגדרות של struct או union, להגדרות של סוגים של enum ול-typedef. בשמות האלה, צריך להשתמש ב-UpperCamelCase/PascalCase. דוגמאות:

enum NfcStatus : int32_t {
    /*...*/
};
struct NfcData {
    /*...*/
};

ערכי enum

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

enum NfcStatus : int32_t {
    HAL_NFC_STATUS_OK               = 0,
    HAL_NFC_STATUS_FAILED           = 1,
    HAL_NFC_STATUS_ERR_TRANSPORT    = 2,
    HAL_NFC_STATUS_ERR_CMD_TIMEOUT  = 3,
    HAL_NFC_STATUS_REFUSED          = 4
};

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

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

PACKAGE-NAME::UDT[.UDT[.UDT[…]]:ENUM_VALUE_NAME

אסור לכלול רווחים בשם מלא. יש להשתמש בשם מלא רק במקרה הצורך ולהשמיט חלקים מיותרים. דוגמה:

android.hardware.foo@1.0::IFoo.IFooInternal.FooEnum:ENUM_OK

תגובות

לתגובה של שורה אחת, אפשר להשתמש ב-//, ב-/* */ וב-/** */.

// This is a single line comment
/* This is also single line comment */
/** This is documentation comment */
  • שימוש ב-/* */ לתגובות. HIDL תומך ב-// לתגובות, אבל לא מומלץ להשתמש בהן כי הן לא מופיעות בפלט שנוצר.
  • משתמשים ב-/** */ לתיעוד שנוצר. אפשר להחיל אותם רק על הצהרות של ערכים מסוגים, שיטות, שדות ומערכי ערכים. דוגמה:
    /** Replied status */
    enum TeleportStatus {
        /** Object entirely teleported. */
        OK              = 0,
        /** Methods return this if teleportation is not completed. */
        ERROR_TELEPORT  = 1,
        /**
         * Teleportation could not be completed due to an object
         * obstructing the path.
         */
        ERROR_OBJECT    = 2,
        ...
    }
  • כדי להתחיל הערות עם מספר שורות, כותבים /** בשורה נפרדת. משתמשים ב-* בתחילת כל שורה. סוגרים את התגובה ב-*/ בשורה נפרדת, ומציבים את הכוכביות בשורה אחת. דוגמה:
    /**
     * My multi-line
     * comment
     */
  • הודעות לגבי רישוי ורשימות של שינויים צריכות להתחיל בשורה חדשה עם /* (כוכבית אחת), להשתמש ב-* בתחילת כל שורה ולהציב את */ בשורה האחרונה לבד (הכוכביות צריכות להיות באותו קו). דוגמה:
    /*
     * Copyright (C) 2017 The Android Open Source Project
     * ...
     */
    
    /*
     * Changelog:
     * ...
     */

תגובות לקבצים

צריך להתחיל כל קובץ בהודעת הרישוי המתאימה. ל-HALs של הליבה, זה צריך להיות רישיון Apache של AOSP ב-development/docs/copyright-templates/c.txt. חשוב לזכור לעדכן את השנה ולהשתמש בתגובות בכמה שורות בסגנון /* */, כפי שמוסבר למעלה.

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

תגובות TODO

משימות TODO צריכות לכלול את המחרוזת TODO באותיות רישיות ולאחר מכן פסיק נקודה. דוגמה:

// TODO: remove this code before foo is checked in.

מותר להוסיף הערות TODO רק במהלך הפיתוח. אסור להוסיף אותן לממשקים שפורסמו.

הערות על ממשקים ועל פונקציות (docstrings)

משתמשים ב-/** */ למחרוזות docstrings של שורה אחת או כמה שורות. אין להשתמש ב-// למסמכי docstring.

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

/**
 * IFooController is the controller for foos.
 */
interface IFooController {
    /**
     * Opens the controller.
     *
     * @return status HAL_FOO_OK if successful.
     */
    open() generates (FooStatus status);

    /** Close the controller. */
    close();
};

צריך להוסיף @param ו-@return לכל פרמטר או ערך מוחזר:

  • צריך להוסיף את @param לכל פרמטר. אחריו צריך להוסיף את שם הפרמטר ואז את תיאור ה-docstring.
  • צריך להוסיף את @return לכל ערך המוחזר. אחריו צריך להופיע שם הערך המוחזר ואז תיאור הפונקציה.

דוגמה:

/**
 * Explain what foo does.
 *
 * @param arg1 explain what arg1 is
 * @param arg2 explain what arg2 is
 * @return ret1 explain what ret1 is
 * @return ret2 explain what ret2 is
 */
foo(T arg1, T arg2) generates (S ret1, S ret2);

כללי עיצוב

כללי עיצוב כלליים כוללים:

  • אורך הקו. כל שורה של טקסט צריכה להיות באורך של עד 100 עמודות.
  • רווחים. אין רווחים לבנים בסוף השורות. שורות ריקות לא יכולות להכיל רווחים לבנים.
  • מרחבים משותפים לעומת כרטיסיות יש להשתמש רק ברווחים.
  • Indent size (גודל הפסקה). משתמשים ב4 רווחים בין בלוקים וב8 רווחים בין שורות
  • תמיכה. מלבד ערכי הערה, סוגר פתוח מופיע באותה שורה שבה מופיע הקוד הקודם, אבל סוגר סגור והנקודה-פסיק הבאה תופסים את כל השורה. דוגמה:
    interface INfc {
        close();
    };

הצהרת חבילה

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

package PACKAGE-NAME;

דוגמה:

package android.hardware.nfc@1.0;

הצהרות על פונקציות

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

interface IFoo {
    /** ... */
    easyMethod(int32_t data) generates (int32_t result);
};

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

interface IFoo {
    suchALongMethodThatCannotFitInOneLine(int32_t theFirstVeryLongParameter,
                                          int32_t anotherVeryLongParameter);
    anEvenLongerMethodThatCannotFitInOneLine(int32_t theFirstLongParameter,
                                             int32_t anotherVeryLongParameter)
                                  generates (int32_t theFirstReturnValue,
                                             int32_t anotherReturnValue);
    superSuperSuperSuperSuperSuperSuperLongMethodThatYouWillHateToType(
            int32_t theFirstVeryLongParameter, // 8 spaces
            int32_t anotherVeryLongParameter
        ) generates (
            int32_t theFirstReturnValue,
            int32_t anotherReturnValue
        );
    /* method name is even shorter than 'generates' */
    foobar(AReallyReallyLongType aReallyReallyLongParameter,
           AReallyReallyLongType anotherReallyReallyLongParameter)
        generates (ASuperLongType aSuperLongReturnValue, // 4 spaces
                   ASuperLongType anotherSuperLongReturnValue);
}

פרטים נוספים:

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

הערות

יש להשתמש בפורמט הבא להערות:

@annotate(keyword = value, keyword = {value, value, value})

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

@callflow(key = value)
@entry
@exit

חשוב לוודא שההערה תופסת את כל השורה. לדוגמה:

/* Good */
@entry
@exit

/* Bad */
@entry @exit

אם ההערות לא יכולות להופיע באותו שורה, צריך להוסיף 8 רווחים. דוגמה:

@annotate(
        keyword = value,
        keyword = {
                value,
                value
        },
        keyword = value)

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

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

/* Good */
@callflow(key = {"val", "val"})

/* Bad */
@callflow(key = { "val","val" })

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

/* Good */
@entry
foo();

/* Bad */
@entry

foo();

הצהרות על טיפוסים בני מנייה (enum)

יש להשתמש בכללים הבאים להצהרות על enum:

  • אם אתם משתפים את ההצהרות על enum עם חבילת קוד אחרת, הניחו את ההצהרות ב-types.hal במקום להטמיע אותן בתוך ממשק.
  • מוסיפים רווח לפני ואחרי הנקודתיים, ורווחים אחרי הסוג הבסיסי לפני סוגר הסוגריים הפתוח.
  • יכול להיות שלא יהיה פסיק נוסף בערך enum האחרון.

הצהרות על struct

יש להשתמש בכללים הבאים להצהרות על מבני struct:

  • אם אתם משתפים את ההצהרות על המבנה עם חבילת קוד אחרת, הניחו את ההצהרות ב-types.hal במקום להטמיע אותן בתוך ממשק.
  • צריך להוסיף רווח אחרי שם סוג המבנה לפני סוגר הפתיחה.
  • מרכזים את שמות השדות (אופציונלי). דוגמה:
    struct MyStruct {
        vec<uint8_t>   data;
        int32_t        someInt;
    }

הצהרות על מערכי נתונים

אין להוסיף רווחים בין הפריטים הבאים:

  • סוג הרכיב וסוגריים מרובעים פתוחים.
  • סוגר מרובע פתוח וגודל המערך.
  • גודל המערך וסוגר מרובע סגור.
  • סוגר מרובע סגור וסגר מרובע פתוח נוסף, אם יש יותר ממאפיין אחד.

לדוגמה:

/* Good */
int32_t[5] array;

/* Good */
int32_t[5][6] multiDimArray;

/* Bad */
int32_t [ 5 ] [ 6 ] array;

וקטורים

אין להוסיף רווחים בין הפריטים הבאים:

  • vec וסוגר זוויתי שמאלי.
  • סוגר זוויתי שמאלי וסוג רכיב (הערה: סוג הרכיב הוא גם vec).
  • סוג הרכיב וסגן תיבת האסימון (הערה: סוג הרכיב הוא גם vec).

לדוגמה:

/* Good */
vec<int32_t> array;

/* Good */
vec<vec<int32_t>> array;

/* Good */
vec< vec<int32_t> > array;

/* Bad */
vec < int32_t > array;

/* Bad */
vec < vec < int32_t > > array;