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

סגנון הקוד HIDL דומה לקוד C++ במסגרת Android, עם 4 רווחים כניסת פסקה ושמות קבצים מעורבים. הצהרות על חבילות, ייבוא ו-docstring דומים לאלו שב-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.
  • אין לכלול רווחים.

ה-FQN תמיד משמש בהצהרות על חבילות.

גרסאות

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

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 אם סוג הייבוא זהה חבילה. FQN לא יכול להכיל רווחים. דוגמה לשם שמוגדר במלואו:

android.hardware.nfc@1.0::INfcClientCallback

בקובץ אחר תחת android.hardware.nfc@1.0, יש לעיין ב מעל לממשק כ-INfcClientCallback. אחרת, משתמשים רק מוגדר במלואו.

ייבוא של קבוצות והזמנות

צריך להוסיף שורה ריקה אחרי הצהרת החבילה (לפני הייבוא). כל ייבוא צריכות לתפוס שורה אחת ולא להוסיף כניסת פיסקה. ייבוא קבוצתי ב הסדר הבא:

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

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

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;
}

שמות של סוגי תווים

שמות של סוגים מתייחסים להגדרות של מבני ואיחוד, הגדרות של סוג טיפוס טיפוסים בני מנייה (enum) ו typedef שנ'. בשמות האלה, משתמשים UpperCamelCase מתוך PascalCase. למשל:

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

ערכים של טיפוסים בני מנייה (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 לבין שם של ערך 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 תומך ב-// לתגובות, לא מומלץ להוסיף אותם כי הם לא מופיעים בפלט שנוצר.
  • אפשר להשתמש ב-/** */ למסמכי התיעוד שנוצרו. אפשר להחיל את האפשרויות האלה רק להצהרות מסוג 'סוג', 'שיטה', 'שדה' ו'ערך טיפוסים בני מנייה (enum)'. דוגמה:
    /** 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:
     * ...
     */
    

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

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

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

תגובות TODO

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

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

תגובות TODO מותרות רק במהלך הפיתוח; הם חייבים לא קיימות בממשקים שפורסמו.

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

כדי להשתמש ב-docstrings עם מספר שורות ושורה אחת, צריך להשתמש ב-/** */. אני לא רוצה להשתמש // לקובצי docstring.

קובצי Docstring לממשקים צריכים לתאר מנגנונים כלליים של ממשק, רציונליות עיצוב, מטרה וכו'. Docsstring לפונקציות צריך להיות ספציפי לפונקציה (תיעוד ברמת החבילה נכנס לקובץ 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 לכל ערך של החזרה. הוא אחרי הערך המוחזר צריך להופיע השם של הערך המוחזר ואז ה-docstring.

דוגמה:

/**
 * 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 עמודות.
  • רווחים לבנים. ללא רווח לבן בסוף הקווים; שורות ריקות אסור לכלול רווחים לבנים.
  • מרחבים לעומת כרטיסיות. מותר להשתמש רק במרחבים משותפים.
  • גודל כניסת פיסקה. צריך להשתמש ב-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 האחרון אין פסיק מיותר.

הצהרות מבנה

צריך להשתמש בכללים הבאים להצהרות Build:

  • אם הצהרות מבנה משותפות עם חבילה אחרת, צריך להוסיף את ההצהרות ב-types.hal במקום להטמיע בתוך ממשק.
  • צריך להוסיף רווח אחרי השם של סוג ה-build לפני הסוגריים הפתוחים.
  • יישור שמות של שדות (אופציונלי). דוגמה:
    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;