代码样式指南

HIDL 代码样式类似于 Android 框架中的 C++ 代码,采用 4 个空格的缩进和混合大小写的文件名。软件包声明、导入和文档字符串类似于 Java 中的那些,但略有修改。

以下 IFoo.haltypes.hal 示例演示了 HIDL 代码样式,并提供了指向每种样式详情的快速链接(IFooClientCallback.halIBar.halIBaz.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 应该与版本中描述的版本(主版本号.次版本号)完全相同。
  • 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

MAJORMINOR 版本都应为单个整数。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;
}

类型名称

类型名称指的是结构体或联合定义、枚举类型定义和 typedef。对于这些名称,请使用 UpperCamelCase/PascalCase。示例

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

枚举值

枚举值应为 UPPER_CASE_WITH_UNDERSCORES。当将枚举值作为函数参数传递并将其作为函数返回值返回时,请使用实际的枚举类型(而不是底层整数类型)。示例

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

注意: 枚举类型的底层类型在冒号后显式声明。由于它不依赖于编译器,因此使用实际的枚举类型更清晰。

对于枚举值的完全限定名称,在枚举类型名称和枚举值名称之间使用冒号

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:
     * ...
     */

文件注释

在每个文件开头添加适当的许可声明。对于核心 HAL,这应该是 development/docs/copyright-templates/c.txt 中的 AOSP Apache 许可证。请记住更新年份并使用如上所述的 /* */ 样式多行注释。

您可以选择在许可证声明后放置一个空行,后跟更改日志/版本信息。使用如上所述的 /* */ 样式多行注释,在更改日志后放置空行,然后跟随软件包声明。

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@return

  • 必须为每个参数添加 @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 列。
  • 空格。行尾不得有空格;空行不得包含空格。
  • 空格与制表符。仅使用空格。
  • 缩进大小。块使用 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();

枚举声明

对枚举声明使用以下规则

  • 如果枚举声明与另一个软件包共享,请将声明放在 types.hal 中,而不是嵌入在接口内部。
  • 在冒号前后使用空格,并在底层类型后的左大括号前使用空格。
  • 最后一个枚举值可能没有额外的逗号。

结构体声明

对结构体声明使用以下规则

  • 如果结构体声明与另一个软件包共享,请将声明放在 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;