بستهبندی نرمافزارهای موجود با Nix
یکی از موارد استفاده اصلی Nix، حل مشکلات رایجی است که در بستهبندی نرمافزار با آنها مواجه میشویم؛ مشکلاتی نظیر مشخص کردن و تهیه وابستگیها.
در بلندمدت، Nix چنین مشکلاتی را کاهش میدهد. اما هنگامی که برای نخستین بار یک نرمافزار موجود را با Nix بستهبندی میکنید، مواجهه با خطاهایی که نامفهوم بهنظر میرسند امری رایج است.
مقدمه
در این آموزش، شما نخستین درایویشنهای Nix خود را برای بستهبندی نرمافزارهای C/C++ ایجاد خواهید کرد.
شما از محیط استاندارد Nixpkgs (stdenv) بهره خواهید برد که بخش عمدهای از کارهای مربوطه را خودکارسازی میکند.
چه چیزی خواهید آموخت؟
این آموزش با hello آغاز میشود؛ پیادهسازی برنامهی «hello world» که تنها نیازمند وابستگیهایی است که از پیش توسط stdenv فراهم شدهاند.
در ادامه، بستههای پیچیدهتری را که دارای وابستگیهای خاص خود هستند میسازید، که این امر به استفاده از ویژگیهای اضافی درایویشن منجر میشود.
شما با پیامهای خطای Nix، شکستهای ساخت و مجموعهای از مشکلات دیگر مواجه شده و آنها را رفع خواهید کرد و در طول مسیر، تکنیکهای اشکالزدایی تکرارپذیر خود را توسعه میدهید.
به چه چیزهایی نیاز دارید؟
- آشنایی با شل یونیکس و ویرایشگرهای متن ساده
- باید در خواندن زبان Nix تسلط کافی داشته باشید. در صورت نیاز میتوانید ابتدا به عقب بازگشته و آن آموزش را مرور کنید.
چقدر زمان میبرد؟
گذراندن دقیق تمام مراحل حدود ۶۰ دقیقه طول خواهد کشید.
نخستین بسته شما
نکته
یک بسته مفهومی است که تعریف آزادی دارد و به مجموعهای از فایلها و دادههای دیگر یا به یک عبارت نیکس (Nix expression) که نمایندهی چنین مجموعهای پیش از تحقق یافتن آن است، اشاره دارد. بستهها در Nixpkgs دارای ساختار مرسومی هستند که امکان کشف آنها در جستجوها و ترکیبشان را در محیطها در کنار سایر بستهها فراهم میکند.
برای اهداف این آموزش، یک «بسته» تابعی از زبان Nix است که به یک derivation ارزیابی خواهد شد. این کار شما یا دیگران را قادر میسازد تا در نتیجهی «بستهبندی نرمافزار موجود با Nix»، یک فرآوردهٔ ساخت برای استفادهی عملی تولید کنید.
برای شروع، این derivation اسکلت را در نظر بگیرید:
{ stdenv }:
stdenv.mkDerivation { } این تابعی است که یک مجموعه ویژگی شامل stdenv را دریافت کرده و یک derivation (که در حال حاضر کاری انجام نمیدهد) تولید میکند.
یک تابع بسته
GNU Hello پیادهسازی برنامهی «سلام دنیا» است که کد منبع آن از سرور FTP پروژهی گنو قابل دسترس است.
برای شروع، یک صفت (attribute) به نام pname به مجموعه ارسالی به mkDerivation اضافه کنید.
هر بسته به یک نام و یک نسخه نیاز دارد و نیکس بدون وجود آنها خطای error: derivation name missing را پرتاب خواهد کرد.
stdenv.mkDerivation {
+ pname = "hello";
+ version = "2.12.1";
سپس، یک وابستگی به آخرین نسخه hello اعلام کنید و به Nix دستور دهید تا از fetchzip برای بارگیری آرشیو کد منبع استفاده کند.
نکته
fetchzipمیتواند علاوه بر فایلهای زیپ، آرشیوهای بیشتری را نیز دریافت کند!
هش تا پیش از بارگیری و استخراج آرشیو قابلشناسایی نیست.
اگر هش ارائهشده به fetchzip نادرست باشد، Nix خطا خواهد داد.
صفت hash را روی یک رشتهی خالی تنظیم کنید و سپس با استفاده از پیام خطای حاصل، هش صحیح را مشخص کنید:
# hello.nix
{
stdenv,
fetchzip,
}:
stdenv.mkDerivation {
pname = "hello";
version = "2.12.1";
src = fetchzip {
url = "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz";
sha256 = "";
};
} این فایل را در hello.nix ذخیره کنید و nix-build را اجرا کنید تا اولین خطای ساخت خود را مشاهده کنید:
$ nix-build hello.nix
error: cannot evaluate a function that has an argument without a value ('stdenv')
Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See
/pages/nix-manual/language/constructs#functions.
at /home/nix-user/hello.nix:3:3:
2| {
3| stdenv,
| ^
4| fetchzip, مشکل: عبارت موجود در فایل hello.nix یک تابع است که تنها در صورتی خروجی مورد نظر خود را تولید میکند که آرگومانهای صحیح به آن ارسال شوند.
ساخت با nix-build
مجموعهی stdenv از nixpkgs در دسترس است که باید با یک عبارت Nix دیگر درونریزی شود تا به عنوان یک آرگومان به این derivation ارسال گردد.
روش توصیهشده برای انجام این کار، ایجاد یک فایل default.nix در همان پوشهی hello.nix با محتوای زیر است:
# default.nix
let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-24.05";
pkgs = import nixpkgs { config = {}; overlays = []; };
in
{
hello = pkgs.callPackage ./hello.nix { };
} این به شما اجازه میدهد تا nix-build -A hello را اجرا کنید تا درایویشن موجود در hello.nix را محقق سازید، که مشابه قرار داد فعلی مورد استفاده در Nixpkgs است.
نکته
تابع
callPackageبه طور خودکار صفات را ازpkgsبه تابع دادهشده منتقل میکند، در صورتی که آنها با صفات مورد نیاز مجموعه ویژگی آرگومان آن تابع مطابقت داشته باشند. در این حالت،callPackageمقادیرstdenvوfetchzipرا به تابع تعریفشده درhello.nixتحویل خواهد داد.آموزش callpackage به جزئیات نحوه عملکرد این موضوع میپردازد.
اکنون دستور nix-build را با آرگومان جدید اجرا کنید:
$ nix-build -A hello
error: hash mismatch in fixed-output derivation '/nix/store/pd2kiyfa0c06giparlhd1k31bvllypbb-source.drv':
specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
got: sha256-1kJjhtlsAkpNB7f6tZEs+dbKd8z7KoNHyDHEJ0tmhnc=
error: 1 dependencies of derivation '/nix/store/b4mjwlv73nmiqgkdabsdjc4zq9gnma1l-hello-2.12.1.drv' failed to build پیدا کردن هش فایل
همانطور که انتظار میرفت، هش نادرست فایل باعث بروز خطا شد و Nix به طور مفیدی هش صحیح را ارائه کرد.
در hello.nix، رشتهی خالی را با هش صحیح جایگزین کنید:
# hello.nix
{
stdenv,
fetchzip,
}:
stdenv.mkDerivation {
pname = "hello";
version = "2.12.1";
src = fetchzip {
url = "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz";
sha256 = "sha256-1kJjhtlsAkpNB7f6tZEs+dbKd8z7KoNHyDHEJ0tmhnc=";
};
} اکنون دستور قبلی را دوباره اجرا کنید:
$ nix-build -A hello
this derivation will be built:
/nix/store/rbq37s3r76rr77c7d8x8px7z04kw2mk7-hello.drv
building '/nix/store/rbq37s3r76rr77c7d8x8px7z04kw2mk7-hello.drv'...
...
configuring
...
configure: creating ./config.status
config.status: creating Makefile
...
building
... <many more lines omitted> درایویشن با موفقیت ساخته شد.
خروجی کنسول نشان میدهد که configure فراخوانی شده است، که یک Makefile تولید کرد و سپس از آن برای ساخت پروژه استفاده شد.
در این مورد نیازی به نوشتن هیچگونه دستورالعمل ساختی نبود زیرا سیستم ساخت stdenv مبتنی بر GNU Autoconf است که بهطور خودکار ساختار پوشه پروژه را تشخیص داد.
نتیجه ساخت
پوشه کاری خود را برای دیدن نتیجه بررسی کنید:
$ ls
default.nix hello.nix result این result یک پیوند نمادین (symlink) به مکانی در انبار Nix (Nix store) است که شامل باینری ساختهشده است؛ میتوانید ./result/bin/hello را برای اجرای این برنامه فراخوانی کنید:
$ ./result/bin/hello
Hello, world! تبریک میگوییم، شما با موفقیت اولین برنامه خود را با Nix بستهبندی کردید!
در ادامه، قطعه نرمافزار دیگری را با وابستگیهای خارج از stdenv بستهبندی خواهید کرد که چالشهای جدیدی را پیش رو میگذارد و شما را ملزم به استفاده از ویژگیهای بیشتری از mkDerivation میکند.
بستهای با وابستگیها
اکنون برنامه دوم و تا حدودی پیچیدهاتری را اضافه کنید: icat (که تصاویر را در ترمینال شما رندر میکند).
با افزودن یک صفت جدید برای icat، فایل default.nix را از بخش قبلی تغییر دهید:
# default.nix
let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-24.05";
pkgs = import nixpkgs { config = {}; overlays = []; };
in
{
hello = pkgs.callPackage ./hello.nix { };
icat = pkgs.callPackage ./icat.nix { };
} فایل hello.nix را به یک فایل جدید به نام icat.nix کپی کنید، و صفات pname و version را در آن فایل بهروزرسانی کنید:
# icat.nix
{
stdenv,
fetchzip,
}:
stdenv.mkDerivation {
pname = "icat";
version = "v0.5";
src = fetchzip {
# ...
};
} اکنون نوبت به بارگیری کد منبع میرسد.
مخزن اصلی icat روی GitHub میزبانی میشود، بنابراین باید دریافتکنندهی کد منبع قبلی را جایگزین کنید.
این بار، به جای fetchzip از fetchFromGitHub استفاده کنید و مجموعه ویژگیهای آرگومان تابع را متناسب با آن بهروزرسانی کنید:
# icat.nix
{
stdenv,
fetchFromGitHub,
}:
stdenv.mkDerivation {
pname = "icat";
version = "v0.5";
src = fetchFromGitHub {
# ...
};
} دریافت کد منبع از GitHub
در حالی که fetchzip به آرگومانهای url و sha256 نیاز داشت، آرگومانهای بیشتری برای fetchFromGitHub مورد نیاز است.
URL کد منبع https://github.com/atextor/icat است که همین حالا دو آرگومان اول را به ما میدهد:
owner: نام حسابی که مخزن را کنترل میکند
owner = "atextor"; repo: نام مخزن برای دریافت
repo = "icat"; Navigate to the project's Tags page to find a suitable Git revision (rev), such as the Git commit hash or tag (e.g. v1.0) corresponding to the release you want to fetch.
In this case, the latest release tag is v0.5.
As in the hello example, a hash must also be supplied.
This time, instead of using the empty string and letting nix-build report the correct one in an error, you can fetch the correct hash in the first place with the nix-prefetch-url command.
You need the SHA256 hash of the contents of the tarball (as opposed to the hash of the tarball file itself).
Therefore pass the --unpack and --type sha256 arguments:
$ nix-prefetch-url --unpack https://github.com/atextor/icat/archive/refs/tags/v0.5.tar.gz --type sha256
path is '/nix/store/p8jl1jlqxcsc7ryiazbpm7c1mqb6848b-v0.5.tar.gz'
0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka
```
```
Set the correct hash for `fetchFromGitHub`:
```# icat.nix
{
stdenv,
fetchFromGitHub,
}:
stdenv.mkDerivation {
pname = "icat";
version = "v0.5";
src = fetchFromGitHub {
owner = "atextor";
repo = "icat";
rev = "v0.5";
sha256 = "0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka";
};
}
```
### Missing dependencies
Running `nix-build` on only the new `icat` attribute, an entirely new issue is reported:
```shell
$ nix-build -A icat
these 2 derivations will be built:
/nix/store/86q9x927hsyyzfr4lcqirmsbimysi6mb-source.drv
/nix/store/l5wz9inkvkf0qhl8kpl39vpg2xfm2qpy-icat.drv
...
error: builder for '/nix/store/l5wz9inkvkf0qhl8kpl39vpg2xfm2qpy-icat.drv' failed with exit code 2;
last 10 log lines:
> from /nix/store/hkj250rjsvxcbr31fr1v81cv88cdfp4l-glibc-2.37-8-dev/include/stdio.h:27,
> from icat.c:31:
> /nix/store/hkj250rjsvxcbr31fr1v81cv88cdfp4l-glibc-2.37-8-dev/include/features.h:195:3: warning: #warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE" [8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wcpp-Wcpp8;;]
> 195 | # warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
> | ^~~~~~~
> icat.c:39:10: fatal error: Imlib2.h: No such file or directory
> 39 | #include <Imlib2.h>
> | ^~~~~~~~~~
> compilation terminated.
> make: *** [Makefile:16: icat.o] Error 1
For full logs, run 'nix log /nix/store/l5wz9inkvkf0qhl8kpl39vpg2xfm2qpy-icat.drv'.
```
A compiler error.
The `icat` source was pulled from GitHub, and Nix tried to build what it found, but compilation failed due to a missing dependency: the `imlib2` header.
If you [search for `imlib2` on search.nixos.org](https://search.nixos.org/packages?query=imlib2), you'll find that `imlib2` is already in Nixpkgs.
Add this package to your build environment by adding `imlib2` to the arguments of the function in `icat.nix`.
Then add the argument's value `imlib2` to the list of `buildInputs` in `stdenv.mkDerivation`:
``````nix
# icat.nix
{'{'}
stdenv,
fetchFromGitHub,
imlib2,
{'}'}:
stdenv.mkDerivation {'{'}
pname = "icat";
version = "v0.5";
src = fetchFromGitHub {'{'}
owner = "atextor";
repo = "icat";
rev = "v0.5";
sha256 = "0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka";
{'}'};
buildInputs = [ imlib2 ];
{'}'}
```
```
Run `nix-build -A icat` again and you'll encounter another error, but compilation proceeds further this time:
```shell
$ nix-build -A icat
این derivation ساخته خواهد شد:
/nix/store/bw2d4rp2k1l5rg49hds199ma2mz36x47-icat.drv
...
error: سازنده (builder) برای '/nix/store/bw2d4rp2k1l5rg49hds199ma2mz36x47-icat.drv' با کد خروج 2 با شکست مواجه شد؛
۱۰ خط آخر گزارش (log):
> from icat.c:31:
> /nix/store/hkj250rjsvxcbr31fr1v81cv88cdfp4l-glibc-2.37-8-dev/include/features.h:195:3: warning: #warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE" [8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wcpp-Wcpp8;;]
> 195 | # warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
> | ^~~~~~~
> In file included from icat.c:39:
> /nix/store/4fvrh0sjc8sbkbqda7dfsh7q0gxmnh9p-imlib2-1.11.1-dev/include/Imlib2.h:45:10: fatal error: X11/Xlib.h: No such file or directory
> 45 | #include <X11/Xlib.h>
> | ^~~~~~~~~~~~
> compilation terminated.
> make: *** [Makefile:16: icat.o] Error 1
برای مشاهدهی گزارشهای کامل، دستور 'nix log /nix/store/bw2d4rp2k1l5rg49hds199ma2mz36x47-icat.drv' را اجرا کنید.
```
You can see a few warnings which should be corrected in the upstream code.
But the important bit for this tutorial is `fatal error: X11/Xlib.h: No such file or directory`: another dependency is missing.
## Finding packages
Determining from where to source a dependency is currently somewhat involved, because package names don't always correspond to library or program names.
You need the `Xlib.h` headers from the `X11` C package.
The Nixpkgs derivation for this is `libX11`, available in the `xorg` package set.
There are multiple ways to figure this out:
### `search.nixos.org`
> <span class="admonition-kind" data-kind="tip"></span>
>
> **راهنمایی**
>
> The easiest way to find what you need is on search.nixos.org/packages.
Unfortunately in this case, [searching for `x11`](https://search.nixos.org/packages?query=x11) produces too many irrelevant results because X11 is ubiquitous.
On the left side bar there is a list package sets, and [selecting `xorg`](https://search.nixos.org/packages?buckets={'{'}%22package_attr_set%22%3A[%22xorg%22]%2C%22package_license_set%22%3A[]%2C%22package_maintainers_set%22%3A[]%2C%22package_platforms%22%3A[]{'}'}&query=x11) shows something promising.
In case all else fails, it helps to become familiar with searching the [Nixpkgs source code](https://github.com/nixos/nixpkgs) for keywords.
### Local code search
To find name assignments in the source, search for `"<keyword> ="`.
For example, these are the search results for [`"x11 = "`](https://github.com/search?q=repo%3ANixOS%2Fnixpkgs+%22x11+%3D%22&type=code) or [`"libx11 ="`](https://github.com/search?q=repo%3ANixOS%2Fnixpkgs+%22libx11+%3D%22&type=code) on Github.
Or fetch a clone of the [Nixpkgs repository](https://github.com/nixos/nixpkgs) and search the code locally.
Start a shell that makes the required tools available – `git` for version control, and `rg` for code search (provided by the [`ripgrep` package](https://search.nixos.org/packages?show=ripgrep)):
```shell
$ nix-shell -p git ripgrep
[nix-shell:~]$
```
The Nixpkgs repository is huge.
Only clone the latest revision to avoid waiting a long time for a full clone:
```shell
[nix-shell:~]$ git clone https://github.com/NixOS/nixpkgs --depth 1
...
[nix-shell:~]$ cd nixpkgs/
```
To narrow down results, only search the `pkgs` subdirectory, which holds all the package recipes:
```shell
[nix-shell:~]$ rg "x11 =" pkgs
pkgs/tools/X11/primus/default.nix
21: primus = if useNvidia then primusLib_ else primusLib_.override { nvidia_x11 = null; };
22: primus_i686 = if useNvidia then primusLib_i686_ else primusLib_i686_.override { nvidia_x11 = null; };
pkgs/applications/graphics/imv/default.nix
38: x11 = [ libGLU xorg.libxcb xorg.libX11 ];
pkgs/tools/X11/primus/lib.nix
14: if nvidia_x11 == null then libGL
pkgs/top-level/linux-kernels.nix
573: ati_drivers_x11 = throw "ati drivers are no longer supported by any kernel >=4.1"; # added 2021-05-18;
... <نتایج بسیار بیشتر>
```
Since `rg` is case sensitive by default,
Add `-i` to make sure you don't miss anything:
```
[nix-shell:~]$ rg -i "libx11 =" pkgs
pkgs/applications/version-management/monotone-viz/graphviz-2.0.nix
55: ++ lib.optional (libX11 == null) "--without-x";
pkgs/top-level/all-packages.nix
14191: libX11 = xorg.libX11;
pkgs/servers/x11/xorg/default.nix
1119: libX11 = callPackage ({ stdenv, pkg-config, fetchurl, xorgproto, libpthreadstubs, libxcb, xtrans, testers }: stdenv.mkDerivation (finalAttrs: {
pkgs/servers/x11/xorg/overrides.nix
147: libX11 = super.libX11.overrideAttrs (attrs: {
```
### Local derivation search
To search derivations on the command line, use `nix-locate` from the [`nix-index`](https://github.com/nix-community/nix-index).
### Adding package sets as dependencies
Add `xorg` to your derivation's input attribute set and use `xorg.libX11` in `buildInputs`:
```nix
# icat.nix
{
stdenv,
fetchFromGitHub,
imlib2,
xorg,
}:
stdenv.mkDerivation {
pname = "icat";
version = "v0.5";
src = fetchFromGitHub {
owner = "atextor";
repo = "icat";
rev = "v0.5";
sha256 = "0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka";
};
buildInputs = [ imlib2 xorg.libX11 ];
}
```
> <span class="admonition-kind" data-kind="note"></span>
>
> **نکته**
>
> Because the Nix language is lazily evaluated, accessing only `xorg.libX11` means that the remaining contents of the `xorg` attribute set are never processed.
## Fixing build failures
Run the last command again:
```shell
$ nix-build -A icat
این derivation ساخته خواهد شد:
/nix/store/x1d79ld8jxqdla5zw2b47d2sl87mf56k-icat.drv
...
خطا: سازنده (Builder) مربوط به '/nix/store/x1d79ld8jxqdla5zw2b47d2sl87mf56k-icat.drv' با کد خروج ۲ با شکست مواجه شد؛
۱۰ خط آخر گزارش (Log):
> 195 | # warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
> | ^~~~~~~
> icat.c: In function 'main':
> icat.c:319:33: warning: ignoring return value of 'write' declared with attribute 'warn_unused_result' [8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wunused-result-Wunused-result8;;]
> 319 | write(tempfile, &buf, 1);
> | ^~~~~~~~~~~~~~~~~~~~~~~~
> gcc -o icat icat.o -lImlib2
> در حال نصب
> پرچمهای نصب: SHELL=/nix/store/8fv91097mbh5049i9rglc73dx6kjg3qk-bash-5.2-p15/bin/bash install
> make: *** No rule to make target 'install'. Stop.
برای مشاهدهی گزارشهای کامل، دستور 'nix log /nix/store/x1d79ld8jxqdla5zw2b47d2sl87mf56k-icat.drv' را اجرا کنید.
```
The missing dependency error is solved, but there is now another problem: `make: *** No rule to make target 'install'. Stop.`
### `installPhase`
`stdenv` is automatically working with the `Makefile` that comes with `icat`.
The console output shows that `configure` and `make` are executed without issue, so the `icat` binary is compiling successfully.
The failure occurs when the `stdenv` attempts to run `make install`.
The `Makefile` included in the project happens to lack an `install` target.
The `README` in the `icat` repository only mentions using `make` to build the tool, leaving the installation step up to users.
To add this step to your derivation, use the [`installPhase` attribute](https://nixos.org/manual/nixpkgs/stable/#ssec-install-phase).
It contains a list of command strings that are executed to perform the installation.
Because `make` finishes successfully, the `icat` executable is available in the build directory.
You only need to copy it from there to the output directory.
In Nix, the output directory is stored in the `$out` variable.
That variable is accessible in the derivation's [`builder` execution environment](/pages/nix-manual/language/derivations#builder-execution).
Create a `bin` directory within the `$out` directory and copy the `icat` binary there:
```# icat.nix
{
stdenv,
fetchFromGitHub,
imlib2,
xorg,
}:
stdenv.mkDerivation {
pname = "icat";
version = "v0.5";
src = fetchFromGitHub {
owner = "atextor";
repo = "icat";
rev = "v0.5";
sha256 = "0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka";
};
buildInputs = [ imlib2 xorg.libX11 ];
installPhase = ''
mkdir -p $out/bin
cp icat $out/bin
''
}
```
### Phases and hooks
Nixpkgs `stdenv.mkDerivation` derivations are separated into [phases](https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases).
Each is intended to control some aspect of the build process.
Earlier you observed how `stdenv.mkDerivation` expected the project's `Makefile` to have an `install` target, and failed when it didn't.
To fix this, you defined a custom `installPhase` containing instructions for copying the `icat` binary to the correct output location, in effect installing it.
Up to that point, the `stdenv.mkDerivation` automatically determined the `buildPhase` information for the `icat` package.
During derivation realisation, there are a number of shell functions ("hooks", in Nixpkgs) which may execute in each derivation phase.
Hooks do things like set variables, source files, create directories, and so on.
These are specific to each phase, and run both before and after that phase's execution.
They modify the build environment for common operations during the build.
Include calls to these hooks in the derivation phases you define, even when you don't make direct use of them.
This facilitates easy [overriding](https://nixos.org/manual/nixpkgs/stable/#chap-overrides) of specific parts of the derivation later.
And it keeps the code tidy and makes it easier to read.
Adjust your `installPhase` to call the appropriate hooks:
```nix
# icat.nix
# ...
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp icat $out/bin
runHook postInstall
'';
# ...
```
## A successful build
Running the `nix-build -A icat` command once more will finally do what you want, repeatably.
Call `ls` in the local directory to find a `result` symlink to a location in the Nix store:
```shell
$ ls
default.nix hello.nix icat.nix result
```
فایل `result/bin/icat` همان فایل اجرایی است که پیشتر ساخته شد. موفقیتآمیز بود!
اجرای `nix-build` (بدون مشخص کردن یک صفت) تمام صفتها را بهطور همزمان میسازد.
اولین صفت (`hello`) در مسیر `result/bin/` ظاهر میشود، در حالی که دومین صفت (`icat`) در مسیر `result-2/bin/` قرار میگیرد.
افزودن صفتهای بیشتر باعث ایجاد پیوندهای نمادین (symlinks) اضافی به شکل `result-n` میشود.
## منابع
- [راهنمای Nixpkgs - محیط استاندارد](https://nixos.org/manual/nixpkgs/unstable/#part-stdenv)
## گامهای بعدی
- [callpackage-tutorial](/pages/nix-dev/tutorials/callpackage)
- [sharing-dependencies](/pages/nix-dev/guides/recipes/sharing-dependencies)
- [automatic-direnv](/pages/nix-dev/guides/recipes/direnv)
- [python-dev-environment](/pages/nix-dev/guides/recipes/python-environment)
- [افزودن بستههای جدید خود به Nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md)
- [how-to-contribute](/pages/nix-dev/contributing/how-to-contribute)
- [how-to-get-help](/pages/nix-dev/contributing/how-to-get-help)