فا نیکسی

4.4. Derivation انبار و مسیر Deriving

علاوه بر عمل کردن به عنوان یک انبار محتوا-محور، لایه انبار Nix به عنوان یک سیستم ساخت نیز کار می‌کند. سایر سیستم‌ها (مانند Git یا IPFS) نیز داده‌های تغییرناپذیر را ذخیره و منتقل می‌کنند، اما کاری به این ندارند که آن داده‌ها چگونه ایجاد شده‌اند.

این همان نقطه‌ای است که Nix خود را متمایز می‌کند. درایویشن‌ها نشان‌دهنده‌ی مراحل ساخت فردی هستند، و مسیرهای مشتق‌سازی (deriving paths) برای ارجاع به خروجی‌های آن مراحل ساخت پیش از ساخته شدنشان مورد نیازند.

درایویشن انبار

یک درایویشن، مشخصاتی برای اجرای یک فایل اجرایی روی ورودی‌های دقیقاً تعریف‌شده به‌منظور تولید یک یا چند شیء انبار است. این اشیاء انبار به عنوان خروجی‌های درایویشن شناخته می‌شوند.

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

یک درایویشن شامل موارد زیر است:

ارجاع به درایویشن‌ها

به درایویشن‌ها همیشه با استفاده از [مسیر انبار] شیء انباری که در آن کدگذاری (encoding) شده‌اند، ارجاع داده می‌شود. برای جزئیات بیشتر در مورد نحوه کار این کدگذاری و در نتیجه اینکه دقیقاً چه مسیر انباری را برای یک درایویشن خاص خواهیم داشت، به بخش کدگذاری مراجعه کنید.

مسیر انبار شیء انباری که یک درایویشن را کدگذاری می‌کند، به اختصار اغلب مسیر درایویشن نامیده می‌شود.

مسیر مشتق‌سازی (Deriving path)

مسیرهای مشتق‌سازی روشی برای ارجاع به اشیاء انبار هستند که ممکن است هنوز محقق شده باشند یا نشده باشند. دو فرم وجود دارد:

  • [ثابت]: صرفاً یک [مسیر انبار]. با کپی کردن آن در انبار می‌توان آن را معتبر ساخت: از طریق ارزیاب، رابط خط فرمان یا یک انبار دیگر.

  • [خروجی]: جفتی شامل یک [مسیر انبار] به یک [درایویشن انبار] و یک نام [خروجی].

در کد شبه‌کد (Pseudo code):

type OutputName = String;

type ConstantPath = {
  path: StorePath;
};

type OutputPath = {
  drvPath: StorePath;
  output: OutputName;
};

type DerivingPath = ConstantPath | OutputPath;

مسیرهای deriving به این دلیل ضروری هستند که به‌طور کلی، و به‌ویژه برای derivationهای محتوا-آدرس‌پذیر، مسیر انبار یک خروجی از پیش مشخص نیست. ما می‌توانیم به جای مسیر انبار که هنوز آن را نمی‌شناسیم، از یک مسیر deriving خروجی برای ارجاع به چنین خروجی‌ای استفاده کنیم.

بخش‌های یک derivation

یک derivation از بخش‌هایی تشکیل شده است که در زیربخش‌های بعدی مستند شده‌اند.

ورودی‌ها

ورودی‌ها مجموعه‌ای از مسیرهای deriving هستند که به تمام اشیاء انبار مورد نیاز برای انجام این مرحله ساخت (build) اشاره می‌کنند.

فیلدهای ایجاد فرآیند احتمالاً شامل بسیاری از مسیرهای انبار خواهند بود:

  • مسیر فایل اجرایی معمولاً با یک مسیر انبار شروع می‌شود.
  • آرگومان‌ها و متغیرهای محیطی احتمالاً حاوی مسیرهای انبار دیگری هستند.

اما به جای جستجوی خودکار در سایر فیلدها برای یافتن ورودی‌ها، Nix ایجاب می‌کند که تمام ورودی‌ها به‌طور صریح در فیلد ورودی‌ها جمع‌آوری شوند. در عوض، مسئولیت سازنده یک derivation (مثلاً ارزیاب) است که اطمینان حاصل کند هر شیء انباری که در فیلد دیگری به آن ارجاع داده شده است (مثلاً با مسیر انبار ارجاع داده شده)، در این فیلد ورودی‌ها گنجانده شده است.

سیستم

نوع سیستمی که فایل اجرایی builder قرار است روی آن اجرا شود.

یک شرط لازم برای اینکه Nix بتواند یک derivation مشخص را روی برخی از [نمونه‌های Nix][Nix instance] زمان‌بندی کند، این است که «سیستم» آن derivation با گزینه پیکربندی system یا گزینه پیکربندی extra-platforms آن نمونه مطابقت داشته باشد.

Nix با قرار دادن system در هر derivation، برنامه‌های ساخت ناهمگن (heterogeneous) را امکان‌پذیر می‌سازد؛ جایی که همه مراحل را نمی‌توان روی یک ماشین یا یک نوع ماشین اجرا کرد. Nix می‌تواند ساخت‌ها را به‌گونه‌ای زمان‌بندی کند که به‌طور خودکار روی پلتفرم‌های دیگر ساخته شوند، و این کار را از طریق [ارسال درخواست‌های ساخت به جلو][forwarding build requests](/pages/nix-manual/advanced-topics/distributed-builds) به سایر نمونه‌های Nix انجام می‌دهد.

فیلدهای ایجاد فرآیند

این‌ها سه فیلدی هستند که نحوه راه‌اندازی فرآیندی را توصیف می‌کنند که (به همراه هر یک از زیرفرآیندهای خودش) ساخت را انجام خواهد داد. ممکن است متوجه شده باشید که این موارد شامل تمام چیزهای لازم برای یک سیست‌کال execve است.

سازنده

این مسیر یک فایل اجرایی است که ساخت را انجام داده و خروجی‌ها را تولید می‌کند.

آرگومان‌ها

آرگومان‌های خط فرمان که به فایل اجرایی builder ارسال می‌شوند.

توجه داشته باشید که این‌ها آرگومان‌های پس از اولین آرگومان هستند. طبق قرارداد معمول در یونیکس، اولین آرگومان ارسال‌شده به builder مقدار خود builder خواهد بود. برای جزئیات بیشتر به ویکی‌پدیا مراجعه کنید.

متغیرهای محیطی

متغیرهای محیطی که به فایل اجرایی builder ارسال خواهند شد.

صفات ساختاریافته

Nix همچنین از پشتیبانی ویژه‌ای برای جاسازی JSON در درون derivationها برخوردار است.

متغیر محیطی NIX_ATTRS_JSON_FILE به مکان دقیق آن فایل هم در طول یک ساخت و هم در nix-shell اشاره می‌کند.

به‌عنوان یک تسهیلות برای سازنده‌های Bash، نیکس اسکریپتی را می‌نویسد که متغیرهای شل مربوط به تمام صفت‌هایی که قابل نمایش در Bash هستند را مقداردهی اولیه می‌کند. متغیر محیطی NIX_ATTRS_SH_FILE به مکان دقیق این اسکریپت، هم در یک ساخت و هم در یک nix-shell اشاره می‌کند. این شامل آرایه‌های غیرتودرتو (انجمنی) نیز می‌شود. برای مثال، صفت hardening.format = true در نهایت به‌صورت عنصر آرایه انجمنی Bash یعنی ${'{'}hardening[format]{'}'} درمی‌آید.

مکان‌نماها (Placeholders)

مکان‌نماها مقادیر مات و بسته‌ای هستند که در [فزمینه‌های ایجاد فرآیند] برای [اشیاء انبار] استفاده می‌شوند که هنوز [مسیر انبار]های آن‌ها را نمی‌شناسیم. آن‌ها رشته‌هایی به فرم /<hash> هستند که در هر جایی درون رشته‌های آن فزمینه‌ها تعبیه شده‌اند و ما در حال بررسی افزودن مکان‌نماهایی شبیه به مسیر انبار هستیم.

نکته

مسیر مشتق‌ساز خروجی (Output Deriving Path) برای حل همان مشکل مکان‌نماها وجود دارد — یعنی، ارجاع به اشیاء انباری که هنوز مسیر انبار آن‌ها را نمی‌شناسیم. آن‌ها همچنین دارای یک نحو رشته‌ای با ^ هستند که در بخش رمزگذاری شرح داده شده است. ما می‌توانیم به‌جای /<hash> برای مکان‌نماها از آن نحو استفاده کنیم، اما خوانایی آن برای انسان مشکل‌ساز خواهد شد.

دو نوع مکان‌نما وجود دارد که با دو حالتی که این مشکل در آن‌ها رخ می‌دهد مطابقت دارند:

  • [مکان‌نمای خروجی]:

    این یک مکان‌نما برای خروجی خودِ یک درایویشن است.

  • [مکان‌نمای ورودی]:

    این یک مکان‌نما برای یک [ورودی] غیرثابت درایویشن است، یعنی ورودی‌ای که یک [مسیر مشتق‌شده خروجی] است.

توضیح

به‌طور کلی، ما باید یک [شیء انبار] را realise کنیم تا مطمئن شویم شیء انباری برای آن داریم. اما برای این دو حالت، این کار یا غیرممکن است یا غیرعملی:

  • در حالت خروجی، این غیرممکن است:

    ما نمی‌توانیم خروجی را بسازیم تا زمانی که یک درایویشن درست داشته باشیم، و نمی‌توانیم یک درایویشن درست داشته باشیم (بدون استفاده از مکان‌نماها) تا زمانی که مسیر خروجی را داشته باشیم.

  • در حالت ورودی، این غیرعملی است:

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

رمزگذاری (Encoding)

درایویشن

دو فرمت وجود دارد که به‌طور جداگانه مستند شده‌اند:

  • فرمت قدیمی "ATerm"

  • فرمت آزمایشی، در حال توسعه و در حال تغییر JSON

هر درایویشن دارای یک انتخاب متعارف از رمزگذاری است که برای سریال‌سازی آن به یک شیء انبار استفاده می‌شود. این تضمین می‌کند که یک [مسیر انبار] متعارف برای ارجاع به درایویشن وجود دارد، همان‌طور که در ارجاع به درایویشن‌ها شرح داده شده است.

نکته

در حال حاضر، رمزگذاری متعارف برای هر درایویشن فرمت "ATerm" است، اما این موضوع برای انواع درایویشن‌هایی که هنوز پایدار نیستند مشمول تغییر است.

صرف‌نظر از فرمت استفاده‌شده، هنگام سریال‌سازی یک درایویشن به یک شیء انبار، آن شیء انبار محتوا-محور (content-addressed) خواهد بود.

در حالت رایج، ورودی‌های اشیاء انبار یا موارد زیر هستند:

  • مسیرهای مشتق‌ساز ثابت برای اشیاء سورس محتوا-محور، که به‌جای خروجی‌های برخی درایویشن‌های دیگر، «ورودی‌های اولیه» هستند

  • خروجی‌های سایر درایویشن‌ها

اگر آن درایویشن‌های دیگر نیز از این حالت مشترک پیروی کنند (و همین‌طور برای ورودی‌های تراگذار)، کل بسته شدن (closure) درایویشن سریال‌شده به‌صورت محتواآدرس‌دهی‌شده (content-addressed) خواهد بود.

مسیر مشتق‌شده

  • ثابت

    مسیرهای مشتق‌شده‌ی ثابت، به‌سادگی همانند مسیر زیرین انبار (store path) کدگذاری می‌شوند. بنابراین، می‌بینیم که هر مسیر انبار کدگذاری‌شده‌ای، یک مسیر مشتق‌شده‌ی (ثابت) کدگذاری‌شده‌ی معتبر نیز هست.

  • خروجی

    مسیرهای مشتق‌شده‌ی خروجی با موارد زیر کدگذاری می‌شوند:

    • کدگذاری یک مسیر انبار که به یک درایویشن اشاره دارد

    • یک جداکننده‌ی ^ (یا ! در برخی از بافت‌های قدیمی)

    • نام خروجیِ درایویشنی که در بالا به آن اشاره شد

    مثال

  > /nix/store/lxrn8v5aamkikg6agxwdqd1jz7746wz4-firefox-98.0.2.drv^out
  > ```
>
> این به شکل زیر تجزیه (parse) می‌شود:
>

/nix/store/lxrn8v5aamkikg6agxwdqd1jz7746wz4-firefox-98.0.2.drv^out |------------------------------------------------------------| |-| store path (usual encoding) output name |--| note the ".drv"

گسترش مدل به مرتبه بالاتر

ویژگی آزمایشی: dynamic-derivations

تا اینجای کار، ما برای ارجاع به درایویشن‌ها از مسیرهای انبار استفاده کرده‌ایم. این کار جواب می‌دهد چون به‌طور ضمنی فرض کرده‌ایم که تمام درایویشن‌ها به صورت ایستا (statically) ایجاد می‌شوند؛ یعنی توسط مکانیزم دیگری در خارج از جریان کار ساخته شده و سپس به‌صورت دستی وارد انبار می‌شوند. اما چه می‌شود اگر درایویشن‌ها بتوانند به‌طور پویا درون Nix نیز ایجاد شوند؟ به عبارت دیگر، چه می‌شود اگر درایویشن‌ها بتوانند خروجی درایویشن‌های دیگر باشند؟

نکته

در اصطلاح‌شناسی مقاله «Build Systems à la carte»، ما در حال عمومی‌سازی لایه انبار Nix هستیم تا به جای یک سیستم ساخت «Applicative»، به یک سیستم ساخت «Monadic» تبدیل شود.

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

 type OutputPath = {
-  drvPath: StorePath;
+  drvPath: DerivingPath;
   output: OutputName;
 };

اکنون فیلد drvPath از OutputPath خود یک DerivingPath است و نه یک StorePath.

با این تغییر، تعریف به‌روزرسانی‌شده به شرح زیر است:

type OutputName = String;

type ConstantPath = {
  path: StorePath;
};

type OutputPath = {
  drvPath: DerivingPath;
  output: OutputName;
};

type DerivingPath = ConstantPath | OutputPath;

بر اساس این مدل توسعه‌یافته، DerivingPathها به طور استقرایی از یک ConstantPath ریشه ساخته می‌شوند که با صفر یا چند OutputPath بیرونی احاطه شده‌اند.

رمزگذاری

رمزگذاری به روشی طبیعی تنظیم می‌شود و فیلد drv را به صورت بازگشتی و با استفاده از همان رمزگذاری مسیر اشتقاق، رمزگذاری می‌کند. نتیجه‌ی این کار این است که امکان داشتن زنجیره‌ای از ^<output-name> در انتهای رشته‌ی نهایی وجود دارد، برخلاف اینکه فقط یک مورد منفرد وجود داشته باشد.

مثال

/nix/store/lxrn8v5aamkikg6agxwdqd1jz7746wz4-firefox-98.0.2.drv^foo.drv^bar.drv^out
|----------------------------------------------------------------------------| |-|
inner deriving path (usual encoding)                                           output name
|--------------------------------------------------------------------| |-----|
even more inner deriving path (usual encoding)                         output name
|------------------------------------------------------------| |-----|
innermost constant store path (usual encoding)                 output name

[[Nix instance]]: glossary.md#gloss-nix-instance

nix.dev/manual/nix/stable/store/derivation/

نیکسی · یادداشت‌های فارسی Nix local fonts