نسخه‌بندی میانا

HIDL نیاز دارد که هر میانای نوشته‌شده در HIDL نسخه‌بندی شود. پس‌از انتشار میانای HAL، این میانا ثابت می‌شود و هرگونه تغییر بعدی باید در نسخه جدیدی از آن میانا اعمال شود. اگرچه نمی‌توان واسط منتشرشده‌ای را تغییر داد، اما می‌توان آن را با واسط دیگری گسترش داد.

ساختار کد HIDL

کد HIDL در انواع، میان‌ها، و بسته‌های تعریف‌شده توسط کاربر سازمان‌دهی می‌شود:

  • انواع تعریف‌شده توسط کاربر (UDT). ‫HIDL دسترسی به مجموعه‌ای از انواع داده‌های ابتدایی را فراهم می‌کند که می‌توان از آن‌ها برای ترکیب انواع پیچیده‌تر ازطریق ساختارها، اتحادیه‌ها، و شمارش‌ها استفاده کرد. UDTها به روش‌های میان‌ها منتقل می‌شوند و می‌توانند در سطح بسته (مشترک برای همه میان‌ها) یا به‌صورت محلی برای یک میان تعریف شوند.
  • میان‌ها. به‌عنوان واحد سازنده پایه HIDL، یک میانای شامل UDT و بیانیه‌های روش است. میاناهای کاربری می‌توانند از میانای کاربری دیگری نیز ارث‌بری کنند.
  • بسته‌ها. میاناهای HIDL مرتبط و انواع داده‌هایی را که روی آن‌ها عمل می‌کنند سازمان‌دهی می‌کند. بسته با نام و نسخه شناسایی می‌شود و شامل موارد زیر است:
    • فایل تعریف نوع داده به‌نام types.hal.
    • صفر یا چند واسط، هرکدام در فایل .hal خود.

فایل تعریف نوع داده types.hal فقط حاوی UDT است (همه UDTهای سطح بسته در یک فایل نگهداری می‌شوند). بازنمایی‌ها به زبان مقصد برای همه میاناهای بسته دردسترس است.

فلسفه نسخه‌بندی

بسته HIDL (مانند android.hardware.nfc)، پس‌از انتشار برای نسخه‌ای خاص (مانند 1.0)، تغییرناپذیر است؛ نمی‌توان آن را تغییر داد. اصلاحات در رابط‌های موجود در بسته یا هرگونه تغییر در UDTهای آن فقط در بسته دیگری می‌تواند انجام شود.

در HIDL، نسخه‌بندی در سطح بسته اعمال می‌شود، نه در سطح میانای کاربری، و همه میاناهای کاربری و UDTها در یک بسته نسخه یکسانی دارند. نسخه‌های بسته از نسخه‌بندی معنایی بدون سطح وصله و عناصر فراداده ساخت پیروی می‌کنند. در یک بسته مشخص، ارتقای نسخه جزئی به این معنی است که نسخه جدید بسته با نسخه قدیمی سازگار با نسخه قدیمی است و ارتقای نسخه عمده به این معنی است که نسخه جدید بسته با نسخه قدیمی سازگار با نسخه قدیمی نیست.

ازنظر مفهومی، یک بسته می‌تواند به یکی از چند روش زیر با بسته دیگری مرتبط باشد:

  • اصلاً.
  • گسترش‌پذیری سازگار با نسخه قدیمی در سطح بسته. این برای نسخه‌های جزئی جدید بسته (بازبینی افزایشی بعدی) رخ می‌دهد؛ بسته جدید همان نام و نسخه عمده بسته قدیمی را دارد، اما نسخه جزئی آن بالاتر است. ازنظر عملکرد، بسته جدید ابرمجموعه‌ای از بسته قدیمی است، یعنی:
    • میاناهای سطح بالای بسته والد در بسته جدید وجود دارد، هرچند ممکن است میانه‌ها روش‌های جدید، UDTهای محلی میانای جدید (افزونه سطح میانای شرح‌داده‌شده در زیر)، و UDTهای جدید در types.hal داشته باشند.
    • میاناهای جدید را نیز می‌توان به بسته جدید اضافه کرد.
    • همه انواع داده بسته والد در بسته جدید وجود دارد و می‌تواند با روش‌های (احتمالاً دوباره پیاده‌سازی‌شده) بسته قدیمی مدیریت شود.
    • انواع داده جدید را نیز می‌توان برای استفاده در روش‌های جدید رابط‌های موجود ارتقایافته یا در رابط‌های جدید اضافه کرد.
  • گسترش‌پذیری سازگار با نسخه قدیمی در سطح میانای. بسته جدید همچنین می‌تواند بسته اصلی را با داشتن میاناهای منطقاً جداگانه که صرفاً عملکرد اضافی ارائه می‌دهند، و نه عملکرد اصلی، گسترش دهد. برای این منظور، موارد زیر ممکن است مطلوب باشد:
    • میاناهای بسته جدید باید به انواع داده بسته قدیمی دسترسی داشته باشند.
    • میاناهای بسته جدید می‌توانند میاناهای یک یا چند بسته قدیمی را گسترش دهند.
  • عدم پس‌سازگاری اصلی را گسترش دهید. این یک نسخه عمده ارتقایافته از بسته است و نیازی به وجود هیچ ارتباطی بین این دو نیست. تا جایی که وجود دارد، می‌توان آن را با ترکیبی از انواع نسخه قدیمی‌تر بسته و وراثت زیرمجموعه‌ای از میاناهای بسته قدیمی بیان کرد.

ساختاردهی میانا

برای داشتن میانایی با ساختار مناسب، افزودن انواع جدیدی از عملکرد که بخشی از طراحی اصلی نیستند باید مستلزم اصلاح میانای HIDL باشد. برعکس، اگر بتوانید یا انتظار داشته باشید که در هر دو طرف میانایی که عملکرد جدیدی را بدون تغییر خود میانجی معرفی می‌کند، تغییر ایجاد کنید، آنگاه میانجی ساختاریافته نیست.

‫Treble از عناصر سیستم و فروشنده که به‌صورت جداگانه تدوین شده‌اند پشتیبانی می‌کند و در آن vendor.img در دستگاه و system.img می‌تواند به‌صورت جداگانه تدوین شود. همه تعاملات بین vendor.img و system.img باید به‌طور صریح و کامل تعریف شود تا بتواند برای سال‌های طولانی به کار خود ادامه دهد. این شامل سطوح API زیادی می‌شود، اما سطح اصلی سازوکار IPC است که HIDL برای ارتباط بین‌پردازشی در مرز system.img/vendor.img استفاده می‌کند.

الزامات

همه داده‌هایی که ازطریق HIDL منتقل می‌شوند باید به‌طور صریح تعریف شوند. برای اطمینان از اینکه پیاده‌سازی و مشتری می‌توانند حتی درصورت جداگانه کامپایل شدن یا توسعه مستقل، با هم کار کنند، داده‌ها باید از الزامات زیر پیروی کنند:

  • می‌تواند مستقیماً در HIDL (بااستفاده از ساختارها، شمارش‌ها، و غیره) با نام‌های معنایی و مفهوم توصیف شود.
  • می‌تواند با استاندارد عمومی مانند ISO/IEC 7816 توصیف شود.
  • می‌تواند با استاندارد سخت‌افزار یا چیدمان فیزیکی سخت‌افزار توصیف شود.
  • درصورت لزوم، می‌تواند داده‌های مبهم (مثل کلیدهای عمومی، شناسه‌ها، و غیره) باشد.

اگر از داده‌های مبهم استفاده می‌شود، فقط یک طرف رابط HIDL باید آن را بخواند. برای مثال، اگر کد vendor.img مؤلفه‌ای را در system.img با پیام رشته‌ای یا vec<uint8_t> داده ارائه دهد، آن داده نمی‌تواند توسط خود system.img تجزیه شود؛ فقط می‌تواند به vendor.img برگردانده شود تا تفسیر شود. وقتی مقدار از vendor.img به کد فروشنده در system.img یا به دستگاه دیگری منتقل می‌شود، قالب داده‌ها و نحوه تفسیر آن باید دقیقاً شرح داده شود و همچنان بخشی از میانای است.

رهنمودها

شما باید بتوانید پیاده‌سازی یا کلاینت HAL را فقط بااستفاده از فایل‌های hal بنویسید (یعنی نباید به منبع Android یا استانداردهای عمومی نگاه کنید). توصیه می‌کنیم رفتار دقیق موردنیاز را مشخص کنید. بیانیه‌هایی مانند «پیاده‌سازی ممکن است A یا B را انجام دهد» پیاده‌سازی‌ها را تشویق می‌کند تا با کارخواهانی که با آن‌ها توسعه یافته‌اند درهم تنیده شوند.

طرح‌بندی کد HIDL

‫HIDL شامل بسته‌های اصلی و فروشنده است.

میاناهای اصلی HIDL میاناهایی هستند که Google مشخص کرده است. بسته‌هایی که به آن‌ها تعلق دارند با android.hardware. شروع می‌شوند و براساس زیرسیستم نام‌گذاری می‌شوند، احتمالاً با سطوح تودرتوی نام‌گذاری. برای مثال، نام بسته NFC android.hardware.nfc و نام بسته دوربین android.hardware.camera است. به‌طورکلی، بسته اصلی نام android.hardware.[name1].[name2]…. بسته‌های HIDL علاوه بر نامشان نسخه هم دارند. برای مثال، بسته android.hardware.camera ممکن است در نسخه 3.4 باشد؛ این موضوع مهم است، زیرا نسخه بسته بر جایگاه آن در درخت منبع تأثیر می‌گذارد.

همه بسته‌های اصلی در سیستم ساخت زیر hardware/interfaces/ قرار می‌گیرند. بسته android.hardware.[name1].[name2]… در نسخه $m.$n تحت hardware/interfaces/name1/name2/…/$m.$n/ است؛ بسته android.hardware.camera نسخه 3.4 در دایرکتوری hardware/interfaces/camera/3.4/. است. نگاشت کدبندی‌شده‌ای بین پیشوند بسته android.hardware. و مسیر hardware/interfaces/ وجود دارد.

بسته‌های غیرهسته‌ای (فروشنده) بسته‌هایی هستند که توسط فروشنده SoC یا ODM تولید می‌شوند. پیشوند برای بسته‌های غیرهسته‌ای vendor.$(VENDOR).hardware. است که در آن $(VENDOR) به فروشنده SoC یا OEM/ODM اشاره دارد. این به مسیر vendor/$(VENDOR)/interfaces در درخت نگاشت می‌شود (این نگاشت نیز سخت‌کدگذاری شده است).

نام‌های نوع تعریف‌شده توسط کاربر کاملاً واجدشرایط

در HIDL، هر UDT دارای یک نام کاملاً واجد شرایط است که شامل نام UDT، نام بسته‌ای که UDT در آن تعریف شده است، و نسخه بسته است. نام کاملاً واجدشرایط فقط زمانی استفاده می‌شود که نمونه‌های نوعی اعلام شده باشند و نه زمانی که خود نوع تعریف شده باشد. برای مثال، فرض کنید نسخه 1.0 بسته android.hardware.nfc, ساختاری به‌نام NfcData را تعریف می‌کند. در سایت اظهارنامه (چه در types.hal یا در اظهارنامه رابط)، اظهارنامه به‌سادگی بیان می‌کند:

struct NfcData {
    vec<uint8_t> data;
};

هنگام تعریف نمونه‌ای از این نوع (چه در ساختار داده و چه به‌عنوان پارامتر روش)، از نام نوع کاملاً واجدشرایط استفاده کنید:

android.hardware.nfc@1.0::NfcData

نحو کلی عبارت است از PACKAGE@VERSION::UDT، که در آن:

  • ‫PACKAGE نام بسته HIDL است که با نقطه از هم جدا شده است (برای نمونه، android.hardware.nfc).
  • VERSION قالب نسخه اصلی.فرعی بسته است که با نقطه از هم جدا شده‌اند (مثلاً 1.0).
  • ‫UDT نام جداشده با نقطه یک HIDL UDT است. ازآنجایی‌که HIDL از UDTهای تودرتو پشتیبانی می‌کند و میاناهای HIDL می‌توانند حاوی UDT (نوعی اعلام تودرتو) باشند، از نقطه برای دسترسی به نام‌ها استفاده می‌شود.

برای مثال، اگر اعلان تودرتوی زیر در فایل انواع رایج در بسته android.hardware.example نسخه 1.0 تعریف شده باشد:

// types.hal
package android.hardware.example@1.0;
struct Foo {
    struct Bar {
        // …
    };
    Bar cheers;
};

نام کاملاً واجدشرایط برای Bar android.hardware.example@1.0::Foo.Bar است. اگر علاوه‌بر اینکه در بسته بالا باشد، اظهارنامه تودرتو در واسطی به‌نام IQuux باشد:

// IQuux.hal
package android.hardware.example@1.0;
interface IQuux {
    struct Foo {
        struct Bar {
            // …
        };
        Bar cheers;
    };
    doSomething(Foo f) generates (Foo.Bar fb);
};

نام کاملاً واجدشرایط برای Bar این است: android.hardware.example@1.0::IQuux.Foo.Bar.

در هر دو مورد، Bar فقط در محدوده اظهارنامه Foo می‌تواند به‌عنوان Bar نامیده شود. در سطح بسته یا میانای، باید ازطریق Foo به Bar ارجاع دهید: Foo.Bar، مانند اعلام روش doSomething در بالا. یا می‌توانید روش را به‌صورت مفصل‌تر به‌صورت زیر تعریف کنید:

// IQuux.hal
doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);

مقادیر فهرست کاملاً واجدشرایط

اگر UDT نوع شمارشی باشد، هر مقدار نوع شمارشی نام کاملی دارد که با نام کامل نوع شمارشی شروع می‌شود، سپس دونقطه و بعداز آن نام مقدار شمارشی می‌آید. برای مثال، فرض کنید بسته android.hardware.nfc, نسخه 1.0 نوع enum NfcStatus را تعریف می‌کند:

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

هنگام ارجاع به STATUS_OK، نام کاملاً واجدشرایط عبارت است از:

android.hardware.nfc@1.0::NfcStatus:STATUS_OK

ساختار کلی به‌صورت زیر است: PACKAGE@VERSION::UDT:VALUE، که در آن:

  • ‫PACKAGE@VERSION::UDT نام کاملاً واجدشرایط دقیقاً یکسانی برای نوع شمارشی است.
  • ‫VALUE نام مقدار است.

قوانین استنباط خودکار

نام UDT کاملاً واجدشرایط نیازی به مشخص شدن ندارد. نام UDT می‌تواند به‌طور ایمن موارد زیر را حذف کند:

  • بسته، مثلاً @1.0::IFoo.Type
  • هم بسته و هم نسخه، برای مثال IFoo.Type

‫HIDL تلاش می‌کند نام را بااستفاده از قوانین تداخل خودکار تکمیل کند (شماره قانون پایین‌تر به‌معنای اولویت بالاتر است).

قانون ۱

اگر بسته و نسخه‌ای ارائه نشود، جستجوی نام محلی انجام می‌شود. مثال:

interface Nfc {
    typedef string NfcErrorMessage;
    send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m);
};

‫NfcErrorMessage به‌صورت محلی جستجو می‌شود و typedef بالای آن پیدا می‌شود. NfcData به‌صورت محلی نیز جستجو می‌شود، اما چون به‌صورت محلی تعریف نشده است، از قانون ۲ و ۳ استفاده می‌شود. @1.0::NfcStatus نسخه‌ای ارائه می‌دهد، بنابراین قانون ۱ اعمال نمی‌شود.

قانون ۲

اگر قانون ۱ اجرا نشود و یکی از عناصر نام کاملاً واجدشرایط وجود نداشته باشد (بسته، نسخه، یا بسته و نسخه)، عنصر با اطلاعات بسته فعلی به‌طور خودکار تکمیل می‌شود. سپس کامپایلر HIDL در فایل فعلی (و همه واردشده‌ها) جستجو می‌کند تا نام کاملاً واجدشرایط تکمیل‌شده خودکار را پیدا کند. بااستفاده از مثال بالا، فرض کنید اعلامیه ExtendedNfcData در همان بسته (android.hardware.nfc) در همان نسخه (1.0) NfcData به‌صورت زیر انجام شده است:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

گردآورنده HIDL نام بسته و نام نسخه را از بسته فعلی تکمیل می‌کند تا نام UDT کاملاً واجدشرایط را تولید کند android.hardware.nfc@1.0::NfcData. ازآنجایی‌که این نام در بسته فعلی وجود دارد (با فرض اینکه به‌درستی وارد شده باشد)، برای اعلان استفاده می‌شود.

نامی در بسته فعلی فقط درصورت درست بودن یکی از موارد زیر وارد می‌شود:

  • به‌طور صریح با عبارت import وارد شده است.
  • در types.hal در بسته کنونی تعریف شده است

اگر NfcData فقط براساس شماره نسخه واجدشرایط شده باشد، همین فرایند دنبال می‌شود:

struct ExtendedNfcData {
    // autofill the current package name (android.hardware.nfc)
    @1.0::NfcData base;
    // … additional members
};

قانون ۳

اگر قانون ۲ نتواند مطابقت پیدا کند (UDT در بسته فعلی تعریف نشده است)، گردآورنده HIDL در همه بسته‌های واردشده به‌دنبال مطابقت می‌گردد. بااستفاده از مثال بالا، فرض کنید ExtendedNfcData در نسخه 1.1 بسته android.hardware.nfc تعریف شده است، 1.1 1.0 را همان‌طور که باید وارد می‌کند (به افزونه‌های سطح بسته مراجعه کنید)، و تعریف فقط نام UDT را مشخص می‌کند:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

گردآورنده به‌دنبال هر UDT به‌نام NfcData می‌گردد و یکی را در android.hardware.nfc در نسخه 1.0 پیدا می‌کند که منجر به یک UDT کاملاً واجدشرایط android.hardware.nfc@1.0::NfcData می‌شود. اگر بیش‌از یک مورد منطبق برای UDT نیمه‌واجدشرایط داده‌شده پیدا شود، کامپایلر HIDL خطا می‌دهد.

مثال

بااستفاده از قانون ۲، نوع واردشده‌ای که در بسته فعلی تعریف شده است بر نوع واردشده از بسته دیگر ترجیح داده می‌شود:

// hardware/interfaces/foo/1.0/types.hal
package android.hardware.foo@1.0;
struct S {};

// hardware/interfaces/foo/1.0/IFooCallback.hal
package android.hardware.foo@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/types.hal
package android.hardware.bar@1.0;
typedef string S;

// hardware/interfaces/bar/1.0/IFooCallback.hal
package android.hardware.bar@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/IBar.hal
package android.hardware.bar@1.0;
import android.hardware.foo@1.0;
interface IBar {
    baz1(S s); // android.hardware.bar@1.0::S
    baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback
};
  • S به‌صورت android.hardware.bar@1.0::S درون‌یابی شده است و در bar/1.0/types.hal پیدا شده است (زیرا types.hal به‌طور خودکار وارد شده است).
  • IFooCallback بااستفاده از قانون ۲ به‌عنوان android.hardware.bar@1.0::IFooCallback میان‌یابی می‌شود، اما نمی‌توان آن را پیدا کرد زیرا bar/1.0/IFooCallback.hal به‌طور خودکار درون‌برد نمی‌شود (مثل types.hal). بنابراین، قانون ۳ آن را به android.hardware.foo@1.0::IFooCallback به‌جای آن حل می‌کند، که ازطریق import android.hardware.foo@1.0; وارد شده است).

types.hal

هر بسته HIDL حاوی یک فایل types.hal است که شامل UDTهایی است که بین همه میاناهای شرکت‌کننده در آن بسته هم‌رسانی می‌شود. انواع HIDL همیشه عمومی هستند؛ صرف‌نظر از اینکه UDT در types.hal یا در یک بیانیه رابط اعلام شده باشد، این انواع در خارج از محدوده تعریف‌شده‌شان قابل‌دسترس هستند. types.hal برای توصیف کردن API عمومی بسته درنظر گرفته نشده است، بلکه برای میزبانی UDTهایی است که همه میاناهای درون بسته از آن‌ها استفاده می‌کنند. به‌دلیل ماهیت HIDL، همه UDTها بخشی از میانای هستند.

‫types.hal از UDT و import بیانیه تشکیل شده است. چون types.hal برای همه واسط‌های بسته دردسترس قرار می‌گیرد (وارد کردن ضمنی است)، این عبارت‌های import طبق تعریف در سطح بسته هستند. UDTها در types.hal همچنین می‌توانند UDTها و میاناهای واردشده را ترکیب کنند.

برای مثال، برای IFoo.hal:

package android.hardware.foo@1.0;
// whole package import
import android.hardware.bar@1.0;
// types only import
import android.hardware.baz@1.0::types;
// partial imports
import android.hardware.qux@1.0::IQux.Quux;
// partial imports
import android.hardware.quuz@1.0::Quuz;

موارد زیر وارد می‌شوند:

  • ‫android.hidl.base@1.0::IBase (به‌طور ضمنی)
  • ‫android.hardware.foo@1.0::types (به‌طور ضمنی)
  • همه چیز در android.hardware.bar@1.0 (شامل همه میان‌ها و types.hal آن)
  • ‫types.hal از android.hardware.baz@1.0::types (میاناهای موجود در android.hardware.baz@1.0 وارد نمی‌شوند)
  • ‫IQux.hal و types.hal از android.hardware.qux@1.0
  • Quuz از android.hardware.quuz@1.0 (با فرض اینکه Quuz در types.hal تعریف شده باشد، کل فایل types.hal تجزیه می‌شود، اما انواع دیگر به‌جز Quuz وارد نمی‌شوند).

نسخه‌بندی در سطح میانا

هر میانای درون بسته در فایل خودش قرار دارد. بسته‌ای که میانای آن متعلق به آن است در بالای میانای بااستفاده از package عبارت اعلام می‌شود. پس‌از اظهارنامه بسته، صفر یا چند واردات سطح رابط (بسته جزئی یا کامل) ممکن است فهرست شود. برای مثال:

package android.hardware.nfc@1.0;

در HIDL، میان‌ها می‌توانند بااستفاده از کلیدواژه extends از میان‌های دیگر ارث ببرند. برای اینکه میانایی میانای دیگری را گسترش دهد، باید ازطریق عبارت import به آن دسترسی داشته باشد. نام میانای درحال گسترش (میانای پایه) از قوانین تعیین صلاحیت نام نوع توضیح‌داده‌شده در بالا پیروی می‌کند. میان‌گیری فقط می‌تواند از یک میان‌گیری ارث ببرد؛ ‫HIDL از ارث‌بری چندگانه پشتیبانی نمی‌کند.

مثال‌های نسخه‌بندی ارتقای زیر از بسته زیر استفاده می‌کنند:

// types.hal
package android.hardware.example@1.0
struct Foo {
    struct Bar {
        vec<uint32_t> val;
    };
};

// IQuux.hal
package android.hardware.example@1.0
interface IQuux {
    fromFooToBar(Foo f) generates (Foo.Bar b);
}

قوانین ارتقا

برای تعریف بسته package@major.minor، یا A یا همه B باید درست باشد:

قانون الف «نسخه فرعی شروع است»: همه نسخه‌های فرعی قبلی، package@major.0،‏ package@major.1،‏ …، package@major.(minor-1) نباید تعریف شده باشند.
یا
قانون B

همه موارد زیر درست باشد:

  1. «نسخه جزئی قبلی معتبر است»: package@major.(minor-1) باید تعریف شود و از همان قانون الف (هیچ‌کدام از package@major.0 تا package@major.(minor-2) تعریف نشده‌اند) یا قانون ب (اگر ارتقای نسخه از @major.(minor-2) باشد) پیروی کند؛

    و

  2. «حداقل یک میانای با نام یکسان را به‌ارث ببرد»: میانای package@major.minor::IFoo وجود دارد که package@major.(minor-1)::IFoo را گسترش می‌دهد (اگر بسته قبلی میانایی داشته باشد)؛

    و

  3. «بدون میانای به‌ارث‌رسیده با نام متفاوت»: نباید package@major.minor::IBar وجود داشته باشد که package@major.(minor-1)::IBaz را گسترش دهد، درحالی‌که IBar و IBaz دو نام متفاوت هستند. اگر واسطی با نام یکسان وجود داشته باشد، package@major.minor::IBar باید package@major.(minor-k)::IBar را گسترش دهد به‌طوری‌که هیچ IBar با k کوچک‌تر وجود نداشته باشد.

به‌دلیل قانون A:

  • بسته می‌تواند با هر شماره نسخه فرعی شروع شود (برای مثال، android.hardware.biometrics.fingerprint از @2.1 شروع می‌شود.)
  • مورد نیاز «android.hardware.foo@1.0 تعریف نشده است» یعنی دایرکتوری hardware/interfaces/foo/1.0 نباید اصلاً وجود داشته باشد.

بااین‌حال، قانون A بر بسته‌ای با همان نام بسته اما با نسخه عمده متفاوت تأثیر نمی‌گذارد (برای مثال، android.hardware.camera.device هم @1.0 و هم @3.2 را تعریف کرده است؛ @3.2 نیازی به تعامل با @1.0 ندارد.) بنابراین، @3.2::IExtFoo می‌تواند @1.0::IFoo را گسترش دهد.

به‌شرطی که نام بسته متفاوت باشد، package@major.minor::IBar می‌تواند از واسطی با نام متفاوت گسترش یابد (برای مثال، android.hardware.bar@1.0::IBar می‌تواند android.hardware.baz@2.2::IBaz را گسترش دهد). اگر واسطی نوع فرادستی را با کلیدواژه extend به‌طور صریح اعلام نکند، android.hidl.base@1.0::IBase را گسترش می‌دهد (به‌جز IBase خودش).

بندهای ب.۲ و ب.۳ باید به‌طور هم‌زمان رعایت شوند. برای مثال، حتی اگر android.hardware.foo@1.1::IFoo گسترش یابد android.hardware.foo@1.0::IFoo تا قانون B.2 را بگذراند، اگر android.hardware.foo@1.1::IExtBar گسترش یابد android.hardware.foo@1.0::IBar، این همچنان ارتقا معتبری نیست.

میاناهای ارتقا

برای ارتقا دادن android.hardware.example@1.0 (تعریف‌شده در بالا) به @1.1:

// types.hal
package android.hardware.example@1.1;
import android.hardware.example@1.0;

// IQuux.hal
package android.hardware.example@1.1
interface IQuux extends @1.0::IQuux {
    fromBarToFoo(Foo.Bar b) generates (Foo f);
}

این import سطح بسته نسخه 1.0 از android.hardware.example در types.hal است. اگرچه در نسخه 1.1 بسته UDT جدیدی اضافه نشده است، ارجاع به UDT در نسخه 1.0 همچنان لازم است، بنابراین وارد کردن سطح بسته در types.hal لازم است. (همین اثر را می‌توانستید با وارد کردن سطح رابط در IQuux.hal به‌دست آورید.)

در extends @1.0::IQuux در بیانیه IQuux، نسخه IQuux را که درحال به‌ارث برده شدن است مشخص کردیم (رفع ابهام لازم است زیرا IQuux برای اعلان کردن میانای و به‌ارث بردن از میانای استفاده می‌شود). ازآنجایی‌که بیانیه‌ها صرفاً نام‌هایی هستند که همه مشخصه‌های بسته و نسخه را در سایت بیانیه به ارث می‌برند، ابهام‌زدایی باید در نام میانای پایه باشد؛ ما می‌توانستیم از UDT کاملاً واجدشرایط نیز استفاده کنیم، اما این کار اضافی بود.

میانای جدید IQuux روشی را که از @1.0::IQuux به‌ارث می‌برد دوباره اعلام نمی‌کند fromFooToBar()؛ این میانای جدید فقط روشی را که اضافه می‌کند فهرست می‌کند fromBarToFoo(). در HIDL، روش‌های به‌ارث‌رسیده نمی‌توانند در واسط‌های فرزند دوباره تعریف شوند، بنابراین واسط IQuux نمی‌تواند روش fromFooToBar() را به‌طور صریح تعریف کند.

قراردادهای Uprev

گاهی اوقات نام‌های میانای باید میانای توسعه‌دهنده را تغییر دهند. توصیه می‌کنیم که افزونه‌های enum، ساختارها، و اتحادها نامی مشابه با آنچه گسترش می‌دهند داشته باشند مگراینکه به‌اندازه کافی متفاوت باشند که نام جدیدی را توجیه کند. نمونه‌ها:

// in parent hal file
enum Brightness : uint32_t { NONE, WHITE };

// in child hal file extending the existing set with additional similar values
enum Brightness : @1.0::Brightness { AUTOMATIC };

// extending the existing set with values that require a new, more descriptive name:
enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };

اگر روشی می‌تواند نام معنایی جدیدی داشته باشد (برای مثال fooWithLocation)، آن نام ترجیح داده می‌شود. درغیراین‌صورت، باید نام آن مشابه چیزی باشد که درحال گسترش است. برای مثال، روش foo_1_1 در @1.1::IFoo می‌تواند عملکرد روش foo در @1.0::IFoo را جایگزین کند، اگر نام جایگزین بهتری وجود نداشته باشد.

نسخه‌بندی در سطح بسته

نسخه‌بندی HIDL در سطح بسته رخ می‌دهد؛ پس‌از انتشار بسته، بسته تغییرناپذیر است (مجموعه رابط‌ها و UDTهای آن قابل تغییر نیست). بسته‌ها می‌توانند به روش‌های مختلفی با یکدیگر مرتبط باشند، که همه آن‌ها ازطریق ترکیبی از وراثت سطح رابط و ساختن UDTها با ترکیب قابل بیان هستند.

بااین‌حال، یک نوع رابطه به‌طور دقیق تعریف شده است و باید اعمال شود: وراثت سازگار با عقب در سطح بسته. در این سناریو، بسته والد بسته‌ای است که از آن ارث‌بری می‌شود و بسته فرزند بسته‌ای است که والد را گسترش می‌دهد. قوانین وراثت سازگار با نسخه قدیمی در سطح بسته به شرح زیر است:

  1. همه میاناهای سطح بالای بسته والد از میاناهای بسته فرزند به ارث می‌رسند.
  2. واسط‌های جدید را نیز می‌توان به بسته جدید اضافه کرد (هیچ محدودیتی درباره روابط با واسط‌های دیگر در بسته‌های دیگر وجود ندارد).
  3. انواع داده‌های جدید را نیز می‌توان برای استفاده توسط روش‌های جدید واسط‌های موجود ارتقایافته یا توسط واسط‌های جدید اضافه کرد.

این قوانین را می‌توان بااستفاده از وراثت سطح میانای HIDL و ترکیب‌بندی UDT پیاده‌سازی کرد، اما برای دانستن اینکه این روابط تشکیل‌دهنده افزونه بسته سازگار با نسخه قدیمی هستند، به دانش سطح متا نیاز است. این دانش به‌صورت زیر استنباط می‌شود:

اگر بسته‌ای این شرط را برآورده کند، hidl-gen قوانین سازگاری با نسخه‌های قدیمی را اعمال می‌کند.