Nix و NixOS برای زیرساخت بازتولیدپذیر، بخش ۲: زبان Nix و کار با Nixpkgs

در بخش قبل با Nix به‌صورت دستوری کار کردیم: نصب، اجرا و rollback. اما قدرت واقعی Nix وقتی آشکار می‌شود که همه‌چیز را با یک زبان توصیف کنیم. در این بخش زبان Nix را در حد نیاز یاد می‌گیریم، در Nixpkgs (بزرگ‌ترین مجموعه‌ی package دنیا) جست‌وجو می‌کنیم، و اولین derivation خودمان را می‌سازیم.

زبان Nix در چند دقیقه

زبان Nix کوچک و functional است. همه‌چیز یک expression است که به یک مقدار ارزیابی می‌شود؛ نه حلقه‌ای، نه دستور مرحله‌به‌مرحله. چند بلوک سازنده کافی است تا بیشتر پیکربندی‌ها را بخوانید: عددها و رشته‌ها (با درج مقدار به شکل ${...})، توابع که همیشه یک ورودی می‌گیرند (currying)، و مهم‌تر از همه attribute set که ساختار داده‌ی اصلی Nix است.

جلسه‌ی nix repl با نمایش رشته، تابع، attribute set و list
یک جلسه‌ی nix repl: درج مقدار در رشته، توابع currying‌شده، دسترسی به فیلد یک attribute set، ادغام دو set با //، و map روی یک list. همین چند مفهوم، پایه‌ی خواندن هر پیکربندی Nix است.

دو ساختار پرکاربرد دیگر هم هست: let ... in برای تعریف مقادیر محلی، و { ... } برای ساختن attribute set. کل Nixpkgs و کل یک سیستم NixOS در نهایت از همین قطعه‌ها ساخته شده‌اند.

Nixpkgs: جست‌وجو و بازرسی

Nixpkgs مجموعه‌ای عظیم از package است که به‌صورت expressionهای Nix تعریف شده‌اند. می‌توانیم در آن جست‌وجو کنیم و بدون نصب، فیلدهای هر package (نسخه، توضیح، مجوز و ...) را ارزیابی کنیم:

جست‌وجو در Nixpkgs با nix search و ارزیابی فیلدهای meta با nix eval
جست‌وجوی ripgrep در Nixpkgs و ارزیابی نسخه و توضیح آن با nix eval. چون همه‌چیز داده است، می‌توان پیش از ساخت، هر ویژگی package را بازرسی کرد.

اولین derivation

یک derivation دستور ساخت یک چیز است؛ ورودی‌ها (کد، وابستگی‌ها، دستورها) را می‌گیرد و خروجی را در یک مسیر تغییرناپذیر /nix/store می‌سازد. لازم نیست از صفر بنویسیم؛ Nixpkgs کمک‌کننده‌های آماده دارد. اینجا با writeShellApplication یک ابزار کوچک می‌سازیم که وابستگی زمان‌اجرایش (hello) را هم به‌صورت اعلانی مشخص می‌کند:

ساخت اولین derivation با writeShellApplication و اجرای نتیجه
یک default.nix که ابزار greet را تعریف می‌کند، ساخت آن با nix-build، و اجرای ./result/bin/greet. خروجی در یک مسیر hash‌دار /nix/store ساخته می‌شود و result فقط یک symlink به آن است.

نکته‌ی کلیدی: چون وابستگی hello به‌صورت runtimeInputs اعلام شده، ابزار ما همیشه دقیقاً همان نسخه را پیدا می‌کند، مستقل از اینکه چه چیزی روی سیستم میزبان نصب است. این همان بازتولیدپذیری است، این‌بار برای چیزی که خودمان ساخته‌ایم.

چرا این مهم است

وقتی package یک expression است، محیط توسعه هم می‌تواند یک expression باشد؛ و یک سیستم کامل هم همین‌طور. در بخش‌های بعد دقیقاً همین را می‌سازیم: ابتدا محیط‌های توسعه‌ی بازتولیدپذیر با flakes، و سپس کل سیستم‌عامل با NixOS.

در بخش بعد به سراغ flakes می‌رویم: راهی استاندارد برای پین‌کردن ورودی‌ها و ساختن محیط‌های توسعه‌ای که روی هر ماشینی یکسان‌اند.

← بازگشت به وبلاگ