סגנון הקוד של HIDL דומה לקוד C++ במסגרת Android, עם הזחות של 4 רווחים ושמות קבצים באותיות רישיות וקטנות. הצהרות על חבילות, ייבוא ומחרוזות תיעוד דומים לאלה ב-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-DIRECTORYMODULESUBMODULE(אופציונלי, יכול להיות יותר מרמה אחת)VERSIONAndroid.mkIINTERFACE_1.halIINTERFACE_2.hal…IINTERFACE_N.haltypes.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/interfacesnfc1.0Android.mkINfc.halINfcClientCallback.haltypes.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 נעשה שימוש בכללים של semantic versioning.
ייבוא
ייבוא יכול להיות באחד משלושת הפורמטים הבאים:
- ייבוא של חבילות שלמות:
import PACKAGE-NAME; - ייבוא חלקי:
import PACKAGE-NAME::UDT;(או, אם הסוג המיובא נמצא באותו חבילה,import UDT; - ייבוא של סוגים בלבד:
import PACKAGE-NAME::types;
הפורמט של PACKAGE-NAME זהה לפורמט של שמות החבילות. החבילה הנוכחית types.hal (אם היא קיימת) מיובאת באופן אוטומטי (אין לייבא אותה באופן מפורש).
שמות מלאים (FQNs)
משתמשים בשמות מלאים לייבוא של סוג שהוגדר על ידי המשתמש רק כשצריך.
משמיטים את PACKAGE-NAME אם סוג הייבוא נמצא באותו חבילה. אסור שיהיו רווחים ב-FQN. דוגמה לשם שמוגדר במלואו:
android.hardware.nfc@1.0::INfcClientCallback
בקובץ אחר, בקטע android.hardware.nfc@1.0, מתייחסים לממשק שלמעלה כאל INfcClientCallback. אחרת, צריך להשתמש רק בשם המלא.
קיבוץ וסידור של ייבוא
משתמשים בשורה ריקה אחרי הצהרת החבילה (לפני הייבוא). כל ייבוא צריך להיות בשורה נפרדת, ללא הזחה. ייבוא קבוצות בסדר הבא:
- חבילות אחרות של
android.hardware(צריך להשתמש בשמות מלאים). - חבילות אחרות של
vendor.VENDOR(צריך להשתמש בשמות מלאים).- כל ספק צריך להיות קבוצה.
- מיון הספקים לפי סדר אלפביתי.
- ייבוא מממשקים אחרים באותו חבילה (שימוש בשמות פשוטים).
משתמשים בשורה ריקה בין קבוצות. בתוך כל קבוצה, ממיינים את הייבוא לפי סדר אלפביתי. דוגמה:
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.hal עם ממשק בשם IFoo. הקובץ הזה יכול להכיל הגדרות רק לממשק IFoo (הממשק INAME צריך להיות ב-INAME.hal).
פונקציות
לשמות של פונקציות, ארגומנטים ומשתני החזרה, משתמשים ב-lowerCamelCase. דוגמה:
open(INfcClientCallback clientCallback) generates (int32_t retVal); oneway pingAlive(IFooCallback cb);
שמות של שדות struct ו-union
בשמות של שדות מסוג struct או union, משתמשים בתו 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 לבין שם ערך ה-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: * ... */
תגובות לקבצים
מתחילים כל קובץ בהודעת הרישוי המתאימה. עבור HALs מרכזיים, הערך הזה צריך להיות רישיון Apache של AOSP בכתובת development/docs/copyright-templates/c.txt.
חשוב לזכור לעדכן את השנה ולהשתמש בהערות מרובות שורות בסגנון /* */, כפי שמוסבר למעלה.
אפשר גם להוסיף שורה ריקה אחרי הודעת הרישיון, ואחריה מידע על שינויים או על ניהול גרסאות. משתמשים בתגובות מרובות שורות בסגנון /* */ כמו שמוסבר למעלה, מציבים את השורה הריקה אחרי יומן השינויים, ואז ממשיכים עם הצהרת החבילה.
תגובות TODO
הערות TODO צריכות לכלול את המחרוזת TODO באותיות רישיות, ואחריה נקודתיים. דוגמה:
// TODO: remove this code before foo is checked in.
אפשר להשתמש בתגובות TODO רק במהלך הפיתוח. אסור שהן יופיעו בממשקים שפורסמו.
תגובות על הממשק ועל הפונקציה (docstrings)
משתמשים ב-/** */ עבור מחרוזות תיעוד מרובות שורות ומחרוזות תיעוד בשורה אחת. אין להשתמש ב-// עבור מחרוזות תיעוד.
מחרוזות התיעוד של ממשקים צריכות לתאר מנגנונים כלליים של הממשק, את ההיגיון העיצובי, המטרה וכו'. מחרוזות התיעוד של פונקציות צריכות להיות ספציפיות לפונקציה (תיעוד ברמת החבילה צריך להיות בקובץ 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(); };
צריך להוסיף @params ו-@returns לכל פרמטר או ערך מוחזר:
- צריך להוסיף את
@paramלכל פרמטר. אחריו צריך להזין את שם הפרמטר ואז את מחרוזת התיעוד. - צריך להוסיף את
@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
הכללים הבאים חלים על הצהרות של מבנים:
- אם הצהרות על מבנים משותפות עם חבילה אחרת, צריך להציב את ההצהרות ב-
types.halבמקום להטמיע אותן בממשק. - משתמשים ברווח אחרי שם סוג ה-struct ולפני הסוגר הפותח.
- התאמה של שמות השדות (אופציונלי). דוגמה:
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;