סגנון הקוד של HIDL דומה לקוד C++ ב-Android framework, עם הזחות של 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-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 נעשה שימוש בכללים של 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; }
הקלדת שמות
שמות סוגים מתייחסים להגדרות של מבנים או איגודים, להגדרות של סוגי 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 רק במהלך הפיתוח. אסור להשתמש בהן בממשקים שפורסמו.
תגובות על ממשקים ופונקציות (מחרוזות תיעוד)
משתמשים ב-/** */
עבור מחרוזות תיעוד מרובות שורות ומחרוזות תיעוד בשורה אחת. אין להשתמש ב-//
עבור מחרוזות תיעוד.
מחרוזות התיעוד של ממשקים צריכות לתאר מנגנונים כלליים של הממשק, את ההיגיון שמאחורי העיצוב, את המטרה וכו'. מחרוזות התיעוד של פונקציות צריכות להיות ספציפיות לפונקציה (תיעוד ברמת החבילה צריך להיות בקובץ 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
s ו-@return
s לכל פרמטר או ערך מוחזר:
- צריך להוסיף את
@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 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
וסוגר זוויתי שמאלי.- סוגר זוויתי שמאלי וסוג הרכיב (הערה: סוג הרכיב הוא גם a
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;