کار با فایلهای محلی
برای ساخت یک پروژه محلی در یک derivation نیکس، کدهای منبع باید برای فایل اجرایی builder آن قابل دسترس باشند.
به طور پیشفرض، builder در یک محیط ایزوله اجرا میشود که تنها اجازه خواندن از انبار نیکس را میدهد.
زبان Nix دارای قابلیتهای توکاری برای کپی کردن فایلهای محلی به انبار و ارائه مسیرهای حاصل در انبار است.
با این حال، استفاده مستقیم از این قابلیتها میتواند چالشبرانگیز باشد:
تبدیل مسیرها به رشتهها (Coercion)، مانند الگوی رایج
src = ./.، درایویشن را به نام پوشه فعلی وابسته میکند. علاوه بر این، همیشه کل پوشه، از جمله فایلهای غیرضروری را به انبار اضافه میکند، که با تغییر آنها باعث ساختهای جدید غیرضروری میشود.تابع
builtins.path(و به طور معادلlib.sources.cleanSourceWith) میتواند این مشکلات را حل کند. با این حال، بیان انتخاب مسیر دلخواه با استفاده از رابط تابعfilterاغلب دشوار است.
در این آموزش شما یاد خواهید گرفت که چگونه از کتابخانه lib.fileset متعلق به Nixpkgs برای کار با فایلهای محلی در درایویشنها استفاده کنید.
این کتابخانه روی قابلیتهای توکار لایه انتزاعی ایجاد میکند و یک رابط امنتر و راحتتر ارائه میدهد.
مجموعهفایلها
یک مجموعهفایل (file set) یک نوع داده است که نمایانگر مجموعهای از فایلهای محلی است. مجموعهفایلها را میتوان با توابع مختلف این کتابخانه ایجاد، ترکیب و دستکاری کرد.
شما میتوانید با استفاده از nix repl کتابخانه را بررسی کرده و درباره آن بیاموزید:
$ nix repl -f channel:nixos-23.11
...
nix-repl> fs = lib.fileset تابع trace فایلهای موجود در یک مجموعه فایل معین را بهصورت قالببندیشده و خوانا نمایش میدهد:
nix-repl> fs.trace ./. null
trace: /home/user (all files in directory)
null تمام توابعی که انتظار یک مجموعهفایل به عنوان آرگومان را دارند، میتوانند یک مسیر را نیز بپذیرند.
چنین آرگومانهای مسیری سپس بهطور ضمنی به مجموعهها تبدیل میشوند که شامل تمام فایلهای موجود در مسیر دادهشده هستند.
در ردگیری (trace) قبلی، این موضوع با (all files in directory) نشان داده شده است.
راهنمایی
تابع
traceاولین آرگومان خود را به صورت خوانا و ساختاریافته چاپ کرده و دومین آرگومان را بازمیگرداند. اما از آنجا که اغلب فقط به این چاپ ساختاریافته درnix replنیاز دارید، میتوانید آرگومان دوم را حذف کنید:nix-repl> fs.trace ./. trace: /home/user (all files in directory) «lambda @ /nix/store/1czr278x24s3bl6qdnifpvm5z03wfi2p-nixpkgs-src/lib/fileset/default.nix:555:8»
با اینکه مجموعهفایلها از نظر مفهومی شامل فایلهای محلی هستند، اما این فایلها هرگز به انبار نیکس اضافه نمیشوند مگر اینکه صراحتاً درخواست شود. بنابراین، لازم نیست چندان نگران کپی شدن تصادفی رازها (secrets) به درون انبار قابلخواندن برای همگان باشید.
در این مثال، با وجود اینکه پوشه خانه را چاپ زیبا و ساختاریافتهاید، هیچ فایلی کپی نشد.
این در تضاد با تبدیل اجباری مسیرها به رشتهها، مانند "${'{'}./.{'}'}" است،
که کل پوشه را در هنگام ارزیابی به انبار نیکس کپی میکند.
هشدار
هنگام استفاده از ویژگیهای آزمایشی
flakesوnix-command، یک پوشه محلی درون یک فلیک همیشه به طور كامل در انبار نیکس کپی میشود، مگر اینکه یک مخزن گیت باشد.
این تبدیل اجباری ضمنی برای فایلها نیز کار میکند:
$ touch some-file nix-repl> fs.trace ./some-file
trace: /home/user
trace: - some-file (regular) علاوه بر فایل موجود، این دستور نوع فایل آن را نیز چاپ میکند.
پروژه نمونه
برای آزمایش بیشتر با کتابخانه، یک پروژه نمونه بسازید.
یک پوشه جدید ایجاد کنید، وارد آن شوید و npins را برای ثابتسازی نسخه Nixpkgs (Pinning) وابستگی Nixpkgs راهاندازی کنید:
$ mkdir fileset
$ cd fileset
$ nix-shell -p npins --run "npins init --bare; npins add github nixos nixpkgs --branch nixos-23.11" سپس یک فایل default.nix با محتوای زیر ایجاد کنید:
default.nix
{
system ? builtins.currentSystem,
sources ? import ./npins,
}:
let
pkgs = import sources.nixpkgs {
config = { };
overlays = [ ];
inherit system;
};
in
pkgs.callPackage ./package.nix { } دو فایل منبع برای کار اضافه کنید:
$ echo hello > hello.txt
$ echo world > world.txt افزودن فایلها به انبار Nix
فایلهای موجود در یک مجموعهفایل مشخص را میتوان با استفاده از toSource به انبار Nix اضافه کرد.
آرگومان این تابع نیازمند یک صفت (attribute) به نام root است تا مشخص کند کدام پوشه منبع باید در انبار کپی شود.
فقط فایلهای موجود در صفت fileset در نتیجه گنجانده میشوند.
فایل package.nix را به صورت زیر تعریف کنید:
package.nix
{ stdenv, lib }:
let
fs = lib.fileset;
sourceFiles = ./hello.txt;
in
fs.trace sourceFiles
stdenv.mkDerivation {
name = "fileset";
src = fs.toSource {
root = ./.;
fileset = sourceFiles;
};
postInstall = ''
mkdir $out
cp -v hello.txt $out
'';
} فراخوانی fs.trace مجموعه فایل را که به عنوان ورودی درایویشن استفاده خواهد شد، چاپ میکند.
ساخت آن را امتحان کنید:
نکته
بار اول دریافت Nixpkgs کمی طول خواهد کشید.
$ nix-build
trace: /home/user/fileset
trace: - hello.txt (regular)
this derivation will be built:
/nix/store/3ci6avmjaijx5g8jhb218i183xi7bi2n-fileset.drv
...
'hello.txt' -> '/nix/store/sa4g6h13v0zbpfw6pzva860kp5aks44n-fileset/hello.txt'
...
/nix/store/sa4g6h13v0zbpfw6pzva860kp5aks44n-fileset اما مزیت واقعی کتابخانه مجموعهفایلها از امکانات آن برای ترکیب مجموعهفایلها به روشهای مختلف ناشی میشود.
تفاوت
برای اینکه بتوانید هر دو فایل hello.txt و world.txt را در خروجی کپی کنید، کل پوشه پروژه را مجدداً به عنوان یک سورس اضافه کنید:
package.nix
{ stdenv, lib }:
let
fs = lib.fileset;
- sourceFiles = ./hello.txt;
+ sourceFiles = ./.;
in
fs.trace sourceFiles
stdenv.mkDerivation {
name = "fileset";
src = fs.toSource {
root = ./.;
fileset = sourceFiles;
};
postInstall = ''
mkdir $out
- cp -v hello.txt $out
+ cp -v {hello,world}.txt $out
'';
} این به طور دلخواه کار خواهد کرد:
$ nix-build
trace: /home/user/fileset (all files in directory)
this derivation will be built:
/nix/store/fsihp8872vv9ngbkc7si5jcbigs81727-fileset.drv
...
'hello.txt' -> '/nix/store/wmsxfgbylagmf033nkazr3qfc96y7mwk-fileset/hello.txt'
'world.txt' -> '/nix/store/wmsxfgbylagmf033nkazr3qfc96y7mwk-fileset/world.txt'
...
/nix/store/wmsxfgbylagmf033nkazr3qfc96y7mwk-fileset با این حال، اگر دوباره nix-build را اجرا کنید، مسیر خروجی متفاوت خواهد بود!
$ nix-build
trace: /home/user/fileset (all files in directory)
this derivation will be built:
/nix/store/nlh7ismrf27xsnl3m20vfz6rvwlbbbca-fileset.drv
...
'hello.txt' -> '/nix/store/xknflcvjaa8dj6a6vkg629zmcrgz10rh-fileset/hello.txt'
'world.txt' -> '/nix/store/xknflcvjaa8dj6a6vkg629zmcrgz10rh-fileset/world.txt'
...
/nix/store/xknflcvjaa8dj6a6vkg629zmcrgz10rh-fileset مشکل اینجاست که nix-build به طور پیشفرض یک پیوند نمادین (symlink) به نام result در پوشه کاری ایجاد میکند که به مسیر انبار تولیدشده اشاره دارد:
$ ls -l result
result -> /nix/store/xknflcvjaa8dj6a6vkg629zmcrgz10rh-fileset از آنجا که src به کل پوشه اشاره دارد و محتویات آن با موفقیت nix-build تغییر میکند، نیکس مجبور خواهد شد هر بار از نو شروع کند.
نکته
این اتفاق بدون کتابخانه مجموعهفایلها نیز رخ میدهد، برای مثال هنگام تنظیم مستقیم
src = ./.;.
تابع difference یک مجموعهفایل را از مجموعهفایل دیگر کم میکند.
نتیجه، یک مجموعهفایل جدید است که شامل تمام فایلهای موجود در آرگومان اول است که در آرگومان دوم وجود ندارند.
از آن برای فیلتر کردن ./result با تغییر تعریف sourceFiles استفاده کنید:
package.nix
{ stdenv, lib }:
let
fs = lib.fileset;
- sourceFiles = ./.;
+ sourceFiles = fs.difference ./. ./result;
in با ساختن این، کتابخانه مجموعهفایلها مشخص خواهد کرد که چه فایلهایی از پوشه برداشته میشوند:
$ nix-build
trace: /home/user/fileset
trace: - package.nix (regular)
trace: - default.nix (regular)
trace: - hello.txt (regular)
trace: - npins (all files in directory)
trace: - world.txt (regular)
this derivation will be built:
/nix/store/zr19bv51085zz005yk7pw4s9sglmafvn-fileset.drv
...
'hello.txt' -> '/nix/store/vhyhk6ij39gjapqavz1j1x3zbiy3qc1a-fileset/hello.txt'
'world.txt' -> '/nix/store/vhyhk6ij39gjapqavz1j1x3zbiy3qc1a-fileset/world.txt'
...
/nix/store/vhyhk6ij39gjapqavz1j1x3zbiy3qc1a-fileset تلاش برای تکرار ساخت (build)، از مسیر انبار (store path) موجود مجدداً استفاده خواهد کرد:
$ nix-build
trace: /home/user/fileset
trace: - package.nix (regular)
trace: - default.nix (regular)
trace: - hello.txt (regular)
trace: - npins (all files in directory)
trace: - world.txt (regular)
/nix/store/vhyhk6ij39gjapqavz1j1x3zbiy3qc1a-fileset فایلهای گمشده
با این حال، حذف پیوند نمادین ./result مشکل جدیدی ایجاد میکند:
$ rm result
$ nix-build
error: lib.fileset.difference: Second argument (negative set)
(/home/user/fileset/result) is a path that does not exist.
To create a file set from a path that may not exist, use `lib.fileset.maybeMissing`. دستورالعملهای موجود در پیام خطا را دنبال کنید و از maybeMissing برای ایجاد یک مجموعهفایل از مسیری که ممکن است وجود نداشته باشد استفاده کنید (در این صورت مجموعهفایل خالی خواهد بود):
package.nix
{ stdenv, lib }:
let
fs = lib.fileset;
- sourceFiles = fs.difference ./. ./result;
+ sourceFiles = fs.difference ./. (fs.maybeMissing ./result);
in این کار اکنون با استفاده از کل پوشه کار میکند، زیرا ./result حضور ندارد:
$ nix-build
trace: /home/user/fileset (all files in directory)
this derivation will be built:
/nix/store/zr19bv51085zz005yk7pw4s9sglmafvn-fileset.drv
...
/nix/store/vhyhk6ij39gjapqavz1j1x3zbiy3qc1a-fileset تلاش مجدد برای ساخت، ردگیری (trace) متفاوتی را تولید خواهد کرد، اما همان مسیر خروجی را به همراه خواهد داشت:
$ nix-build
trace: /home/user/fileset
trace: - package.nix (regular)
trace: - default.nix (regular)
trace: - hello.txt (regular)
trace: - npins (all files in directory)
trace: - world.txt (regular)
/nix/store/vhyhk6ij39gjapqavz1j1x3zbiy3qc1a-fileset اجتماع (استثنا کردن صریح فایلها)
هنوز یک مشکل وجود دارد: تغییر دادن هرکدام از فایلهای گنجاندهشده باعث میشود که derivation مجدداً ساخته شود، با اینکه اصلاً به آن فایلها وابسته نیست.
یک خط خالی به package.nix اضافه کنید:
$ echo >> package.nix بار دیگر، Nix از صفر شروع خواهد کرد:
$ nix-build
trace: /home/user/fileset
trace: - default.nix (regular)
trace: - npins (all files in directory)
trace: - package.nix (regular)
trace: - string.txt (regular)
this derivation will be built:
/nix/store/zmgpqlpfz2jq0w9rdacsnpx8ni4n77cn-filesets.drv
...
/nix/store/6pffjljjy3c7kla60nljk3fad4q4kkzn-filesets یک راه برای رفع این مشکل استفاده از unions است.
یک مجموعه فایل حاوی اشتراک (union) فایلهایی که باید مستثنی شوند بسازید (fs.unions [ ... ])، و آن را (difference) از پوشه کامل (./.) کسر کنید:
package.nix
sourceFiles =
fs.difference
./.
(fs.unions [
(fs.maybeMissing ./result)
./default.nix
./package.nix
./npins
]); این به شکلی که انتظار میرود کار خواهد کرد:
$ nix-build
trace: /home/user/fileset
trace: - hello.txt (regular)
trace: - world.txt (regular)
this derivation will be built:
/nix/store/gr2hw3gdjc28fmv0as1ikpj7lya4r51f-fileset.drv
...
/nix/store/ckn40y7hgqphhbhyrq64h9r6rvdh973r-fileset تغییر دادن هر یک از فایلهای مستثنیشده، دیگر لزوماً باعث ساخت جدید نمیشود:
$ echo >> package.nix $ nix-build
trace: /home/user/fileset
trace: - hello.txt (regular)
trace: - world.txt (regular)
/nix/store/ckn40y7hgqphhbhyrq64h9r6rvdh973r-fileset فیلتر
تابع fileFilter امکان فیلتر کردن مجموعهفایلها را فراهم میکند بهطوریکه هر فایل مشمول، معیارهای دادهشده را برآورده کند.
از آن برای انتخاب تمام فایلهایی که نامشان با .nix ختم میشود استفاده کنید:
package.nix
sourceFiles =
fs.difference
./.
(fs.unions [
(fs.maybeMissing ./result)
- ./default.nix
- ./package.nix
+ (fs.fileFilter (file: file.hasExt "nix") ./.)
./npins
]); این موضوع نتیجه را تغییر نمیدهد، حتی اگر یک فایل .nix جدید اضافه کنیم.
$ nix-build
trace: /home/user/fileset
trace: - hello.txt (regular)
trace: - world.txt (regular)
/nix/store/ckn40y7hgqphhbhyrq64h9r6rvdh973r-fileset بهطور ویژه، رویکرد استفاده از difference ./. بهطور صریح فایلهایی را که باید مستثنی شوند انتخاب میکند، به این معنا که فایلهای جدید اضافه شده به پوشهی منبع بهطور پیشفرض شامل میشوند.
بسته به پروژهی شما، این روش ممکن است نسبت به روش جایگزین در بخش بعدی مناسبتر باشد.
اشتراک (انتخاب صریح فایلها)
در تقابل با روش قبلی، میتوان از unions نیز برای انتخاب فقط فایلهایی که باید شامل شوند استفاده کرد.
این یعنی فایلهای جدید اضافه شده به پوشهی فعلی بهطور پیشفرض نادیده گرفته خواهند شد.
چند فایل اضافی ایجاد کنید:
$ mkdir src
$ touch build.sh src/select.{c,h} سپس مجموعهفایلها را فقط از فایلهایی بسازید که قرار است صراحتاً گنجانده شوند:
package.nix
{ stdenv, lib }:
let
fs = lib.fileset;
sourceFiles = fs.unions [
./hello.txt
./world.txt
./build.sh
(fs.fileFilter
(file: file.hasExt "c" || file.hasExt "h")
./src
)
];
in
fs.trace sourceFiles
stdenv.mkDerivation {
name = "fileset";
src = fs.toSource {
root = ./.;
fileset = sourceFiles;
};
postInstall = ''
cp -vr . $out
'';
} اسکریپت postInstall سادهسازی شده است تا متکی بر این باشد که کدهای منبع به شکل مناسبی ازپیشفیلتر شده باشند:
$ nix-build
trace: /home/user/fileset
trace: - build.sh (regular)
trace: - hello.txt (regular)
trace: - src (all files in directory)
trace: - world.txt (regular)
this derivation will be built:
/nix/store/sjzkn07d6a4qfp60p6dc64pzvmmdafff-fileset.drv
...
'.' -> '/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset'
'./build.sh' -> '/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset/build.sh'
'./hello.txt' -> '/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset/hello.txt'
'./world.txt' -> '/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset/world.txt'
'./src' -> '/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset/src'
'./src/select.c' -> '/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset/src/select.c'
'./src/select.h' -> '/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset/src/select.h'
...
/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset فقط فایلهای مشخصشده استفاده میشوند، حتی زمانی که یک فایل جدید اضافه شود:
$ touch src/select.o README.md
$ nix-build
trace: - build.sh (regular)
trace: - hello.txt (regular)
trace: - src
trace: - select.c (regular)
trace: - select.h (regular)
trace: - world.txt (regular)
/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset تطبیق فایلهای ردیابیشده توسط Git
اگر یک پوشه بخشی از یک مخزن Git باشد، ارسال آن به gitTracked یک مجموعهفایل به شما میدهد که فقط شامل فایلهای ردیابیشده توسط Git است.
یک مخزن محلی Git ایجاد کنید و تمام فایلها به جز src/select.o و ./result را به آن اضافه کنید:
$ git init
Initialized empty Git repository in /home/user/fileset/.git/
$ git add -A
$ git reset src/select.o result برای استفاده مجدد از این مجموعه فایلها با gitTracked:
package.nix
sourceFiles = fs.gitTracked ./.; دوباره آن را بسازید:
$ nix-build
warning: Git tree '/home/user/fileset' is dirty
trace: /home/vg/src/nix.dev/fileset
trace: - README.md (regular)
trace: - package.nix (regular)
trace: - build.sh (regular)
trace: - default.nix (regular)
trace: - hello.txt (regular)
trace: - npins (all files in directory)
trace: - src
trace: - select.c (regular)
trace: - select.h (regular)
trace: - world.txt (regular)
this derivation will be built:
/nix/store/p9aw3fl5xcjbgg9yagykywvskzgrmk5y-fileset.drv
...
/nix/store/cw4bza1r27iimzrdbfl4yn5xr36d6k5l-fileset با این حال، این شامل موارد زیادی میشود، زیرا همه این فایلها برای ساخت derivation به شکلی که در ابتدا در نظر گرفته شده بود، مورد نیاز نیستند.
نکته
هنگام استفاده از ویژگیهای آزمایشی
flakesوnix-command، این تابع مورد نیاز نیست، زیراnix buildبه طور پیشفرض فقط به فایلهایی اجازه دسترسی میدهد که توسط Git ردیابی میشوند. با این حال، برای فراهم کردن تجربه توسعهدهنده یکسان برای Nix پایدار، استفاده از این تابع همچنان توصیه میشود.
اشتراک (Intersection)
اینجاست که intersection وارد میدان میشود.
این تابع امکان ایجاد مجموعهفایلی را فراهم میکند که فقط شامل فایلهایی است که در هر دو مجموعهفایل دادهشده حضور دارند.
تمام فایلهایی را انتخاب کنید که هم توسط Git ردیابی شدهاند و برای ساخت مرتبط هستند:
package.nix
sourceFiles =
fs.intersection
(fs.gitTracked ./.)
(fs.unions [
./hello.txt
./world.txt
./build.sh
./src
]); این همان خروجی روش دیگر را تولید خواهد کرد و بنابراین از یک نتیجهی ساخت قبلی استفاده مجدد میکند:
$ nix-build
warning: Git tree '/home/user/fileset' is dirty
trace: - build.sh (regular)
trace: - hello.txt (regular)
trace: - src
trace: - select.c (regular)
trace: - select.h (regular)
trace: - world.txt (regular)
/nix/store/zl4n1g6is4cmsqf02dci5b2h5zd0ia4r-fileset نتیجهگیری
ما نمونههایی را از نحوهی استفاده از تمام توابع بنیادی مجموعهفایلها نشان دادهایم. برای موارد استفادهی پیچیدهتر، آنها را میتوان در صورت نیاز با یکدیگر ترکیب کرد.
برای مشاهدهی فهرست کامل و جزئیات بیشتر، به مستندات مرجع lib.fileset مراجعه کنید.