コード スタイルガイド

HIDL コードスタイルは、Android フレームワークの C++ コードと同じように、4 スペースのインデントと、大文字と小文字を混ぜたファイル名を使用します。パッケージ宣言、インポート、docstring は 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 は次のとおりです。
    • コア HIDL パッケージの場合は hardware/interfaces
    • ベンダー パッケージの場合は vendor/VENDOR/interfaces。ここで VENDOR は SoC ベンダーまたは OEM / ODM を指します。
  • MODULE には、サブシステムを表す小文字の単語を 1 つ使用します(nfc など)。複数の単語が必要な場合は、ネストされた SUBMODULE を使用します。複数のレベルのネストが可能です。
  • VERSION は、バージョンで記述しているものとまったく同じバージョン(major.minor)にします。
  • IINTERFACE_X は、インターフェース名で記述しているとおり、UpperCamelCase または PascalCaseINfc など)を使用したインターフェース名にします。

例:

  • 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 は次のようになります。
    • コア HIDL パッケージの場合は android.hardwarehardware/interfaces にマッピング)。
    • ベンダー パッケージの場合は vendor.VENDOR.hardware。この VENDOR は、SoC ベンダーまたは OEM / ODM を指します(vendor/VENDOR/interfaces にマッピング)。
  • MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION は、ディレクトリ構造で記述している構造内のものとまったく同じフォルダ名です。
  • パッケージ名は小文字にします。1 単語を超える長さになる場合は、サブモジュールとして使用するか、snake_case で記述する必要があります。
  • スペースは使用できません。

パッケージ宣言では常に FQN が使用されます。

バージョン

バージョンは次の形式にする必要があります。

MAJOR.MINOR

MAJOR バージョンと MINOR バージョンは、いずれも 1 つの整数にします。HIDL では、セマンティック バージョニング ルールを使用します。

インポート

インポートは、次の 3 つの形式のいずれかで行われます。

  • パッケージ全体のインポート: 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 行を使用し、インデントしないでください。インポートのグループ化は次の順序で行います。

  1. その他の android.hardware パッケージ(完全修飾名を使用します)。
  2. その他の vendor.VENDOR パッケージ(完全修飾名を使用します)。
    • 各ベンダーを 1 つのグループにします。
    • ベンダーをアルファベット順に並べます。
  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 インターフェースの定義だけを含めることができます(インターフェース INAMEINAME.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

コメント

1 行のコメントの場合、///* *//** */ を使用できます。

// 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
     */
    
  • ライセンス通知と変更履歴は、新しい行で /*(1 つのアスタリスク)を使用して開始します。各行の先頭で * を使用し、最終行に */ を単独で配置します(アスタリスクをそろえます)。例:
    /*
     * 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 コメントは開発時にのみ使用できます。公開されたインターフェースに存在してはいけません。

インターフェースまたは関数のコメント(docstring)

複数行または 1 行の docstring には /** */ を使用します。// を docstring に使用しないでください。

インターフェース用の docstring には、インターフェースの一般的なメカニズム、設計の根拠、目的などを記述します。関数用の 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 を追加する必要があります。その後に、戻り値の名前、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)

値の配列全体が同じ行に収まらない場合は、開き中かっこ { の後と、配列内の各カンマの後に改行を入れます。最後の値の直後に閉じ丸かっこを配置します。値が 1 つしかない場合、中かっこは使用しないでください。

値の配列全体が同じ行に収まる場合は、開き中かっこの後と閉じ中かっこの前にはスペースを入れず、各カンマの後にスペースを 1 つ入れます。次に例を示します。

/* 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;