סגנון הקוד של 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
. אחרת, צריך להשתמש רק בשם המלא.
קיבוץ והזמנת ייבוא
מוסיפים שורה ריקה אחרי הצהרת החבילה (לפני הייבוא). כל ייבוא צריך להופיע בשורה אחת, בלי הכנסה. צריך לקבץ את הייבוא בסדר הבא:
- חבילות
android.hardware
אחרות (יש להשתמש בשמות מוגדרים במלואם). - חבילות
vendor.VENDOR
אחרות (יש להשתמש בשמות מוגדרים במלואם).- כל ספק צריך להיות קבוצה.
- מיון הספקים לפי סדר אלפביתי.
- ייבוא מממשקי 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;