فصل ۲۳ – کامپایل برنامه‌ها

تا اینجا در سراسر کتاب از برنامه‌هایی استفاده کرده‌ایم که به صورت «بستهٔ دودویی» (binary package) در توزیع‌های لینوکس موجودند.
بیشتر کاربران هم هرگز با ساخت یک برنامه از منبع روبه‌رو نمی‌شوند. بااین‌حال، آشنایی با روند ساخت از منبع مهارتی ارزشمند است؛
گاهی نسخهٔ جدید هنوز در مخازن قرار نگرفته، یا اصلاً بستهٔ آماده‌ای برای نرم‌افزار مورد نظر وجود ندارد،
یا می‌خواهیم قابلیت‌های سفارشی را فعال یا غیرفعال کنیم. در این فصل قدم‌به‌قدم فرآیند کلاسیک «configure → make → make install» را بررسی می‌کنیم،
می‌آموزیم هر مرحله دقیقاً چه می‌کند و چگونه می‌توان با مشکلات رایج کنار آمد.


چرا باید برنامه‌ها را از منبع بسازیم؟

دلایل رایجی که ما را وادار به کامپایل می‌کند عبارت‌اند از:

نکتهٔ خوب این است که بیشتر پروژه‌ها روندی بسیار مشابه دارند،
پس اگر یک بار آن را تمرین کنید، باقی پروژه‌ها نیز برایتان آشنا خواهند بود.

پیش‌نیازها و ابزارهای لازم

ساخت از منبع نیازمند «زنجیرهٔ ابزار توسعه» (toolchain) است:
کامپایلر C یا C++، کتابخانه‌های توسعه، ابزار پیوند (linker)، و برنامهٔ make.
در توزیع‌های مبتنی بر Debian می‌توانید همهٔ ابزارهای پایه را با بستهٔ build-essential نصب کنید:

[me@linuxbox ~]$ sudo apt update
[me@linuxbox ~]$ sudo apt install build-essential

در خانوادهٔ Fedora/Red Hat گروه بستهٔ «Development Tools» یا دستور مشابهی در اختیار دارید:

[me@linuxbox ~]$ sudo dnf groupinstall "Development Tools"

علاوه بر ابزارهای عمومی، هر پروژه ممکن است به هدرها و کتابخانه‌های مخصوصی نیاز داشته باشد.
این وابستگی‌ها معمولاً با پسوند -dev یا -devel شناخته می‌شوند.
وقتی اجرای configure به خطای «header not found» یا «library not found» برخورد می‌کند،
نام بستهٔ مورد نیاز را ذکر می‌کند و می‌توانید آن را نصب کنید.

به‌دست آوردن بستهٔ منبع

نرم‌افزارهای آزاد غالباً به صورت آرشیوهای فشرده در قالب‌هایی مثل tar.gz، tar.bz2 یا tar.xz منتشر می‌شوند.
پس از دانلود، آرشیو را در یک دایرکتوری کاری استخراج می‌کنیم:

[me@linuxbox ~]$ tar xvf tree-1.6.0.tgz
[me@linuxbox ~]$ cd tree-1.6.0

درون دایرکتوری استخراج‌شده معمولاً فایل‌هایی با نام‌های README، INSTALL یا NEWS می‌بینید که باید قبل از ادامه مطالعه شوند.
این فایل‌ها وابستگی‌های اضافی، گزینه‌های پشتیبانی‌شده و نکته‌های ویژهٔ پروژه را توضیح می‌دهند.

ساختار یک بستهٔ مبتنی بر Autotools معمولاً شبیه این است:

configure        فایل اجرایی برای پیکربندی سیستم هدف
configure.ac     ورودی autoconf
Makefile.am      ورودی automake
Makefile.in      قالبی برای ساخت فایل Makefile نهایی
src/             کد منبع برنامه
man/             صفحات راهنما

ما مستقیماً با configure و خروجی آن یعنی Makefile سروکار داریم.
فایل‌های *.in و *.am توسط توسعه‌دهندگان استفاده می‌شوند تا این فرآیند را خودکار کنند.

انجام مراحل استاندارد configure/make/make install

تقریباً همهٔ پروژه‌های مبتنی بر Autotools مراحل زیر را دارند:

  1. اجرای اسکریپت configure برای بررسی سیستم و تولید فایل‌های ساخت.
  2. اجرای make برای کامپایل برنامه.
  3. اجرای make install (معمولاً به صورت ریشه یا با sudo) برای کپی فایل‌های ساخته‌شده در مقصد.

بیایید این مراحل را روی بستهٔ tree انجام دهیم تا روند واقعی را ببینیم.

اجرای configure

در ریشهٔ دایرکتوری منبع دستور زیر را اجرا می‌کنیم:

[me@linuxbox tree-1.6.0]$ ./configure

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

checking for gcc... gcc
checking whether the C compiler works... yes
checking for ncursesw/ncurses.h... yes
checking for ranlib... ranlib
config.status: creating Makefile

اگر وابستگی‌ای یافت نشود، اسکریپت معمولاً متوقف می‌شود و پیام روشنی چاپ می‌کند؛
در آن صورت باید بستهٔ توسعهٔ مربوطه را نصب کرده و دوباره ./configure را اجرا کنید.

اسکریپت configure مجموعهٔ بزرگی از گزینه‌ها دارد. با ./configure --help می‌توانید فهرست کامل را ببینید. متداول‌ترین گزینه‌ها عبارت‌اند از:

مثلاً اگر بخواهیم tree را در شاخهٔ خانگی نصب کنیم، می‌توانیم از ./configure --prefix=$HOME/.local استفاده کنیم.

ساخت با make

پس از موفقیت configure، دستور make را اجرا می‌کنیم:

[me@linuxbox tree-1.6.0]$ make

خروجی make نشان می‌دهد که فایل‌های منبع در حال کامپایل شدن هستند:

gcc -c -O2 -Wall -o tree.o tree.c
gcc -c -O2 -Wall -o unix.o unix.c
gcc -o tree tree.o unix.o -lncurses

در سیستم‌های چند هسته‌ای می‌توانید از گزینهٔ -j برای موازی‌سازی استفاده کنید:

[me@linuxbox tree-1.6.0]$ make -j$(nproc)

بسیاری از پروژه‌ها هدف‌های دیگری مانند make check یا make test را نیز ارائه می‌کنند تا مجموعهٔ آزمون‌ها را پیش از نصب اجرا کنید.
اجرای این آزمون‌ها کمک می‌کند مطمئن شوید برنامه روی سیستم شما به‌درستی کامپایل شده است.

اگر همه چیز به درستی پیش برود، برنامهٔ قابل اجرا در همان دایرکتوری ظاهر می‌شود. می‌توانید بدون نصب هم آن را اجرا کنید:

[me@linuxbox tree-1.6.0]$ ./tree --version
tree v1.6.0 (c) 1996-2023 by Steve Baker, Thomas Moore, Francesc Rocher, and Florian Sesser

نصب برنامه

برای کپی فایل‌ها در سیستم از make install استفاده می‌کنیم. این مرحله معمولاً نیازمند دسترسی ریشه است:

[me@linuxbox tree-1.6.0]$ sudo make install
[sudo] password for me:

فایل‌های نصب‌شده در مقصدی که با --prefix تعیین کرده‌ایم قرار می‌گیرند. اگر پیش‌فرض /usr/local باشد، برنامه در /usr/local/bin/tree و صفحات راهنما در /usr/local/share/man/man1/tree.1 نصب می‌شوند.

بسیاری از پروژه‌ها هدف make uninstall را هم تعریف می‌کنند تا در صورت نیاز فایل‌های نصب‌شده حذف شوند؛ این هدف تنها زمانی کار می‌کند که همان نسخه و همان مسیر نصب را نگه داشته باشید.

بهتر است تمام مراحل تا قبل از make install را به عنوان کاربر عادی انجام دهید. اجرای configure یا make با دسترسی ریشه می‌تواند فایل‌های موقت را با مالکیت root ایجاد کند و در مراحل بعدی دردسرساز شود.

برای حذف فایل‌های واسط (object files) و تمیز کردن دایرکتوری منبع، هدف make clean را اجرا می‌کنیم:

[me@linuxbox tree-1.6.0]$ make clean

بعضی پروژه‌ها هدف make distclean را هم فراهم می‌کنند که فایل‌های تولید شده توسط configure را حذف می‌کند تا دایرکتوری به حالت اولیه برگردد.

اسکریپت configure چه می‌کند؟

configure بخشی از سیستم ساخت GNU Autotools است. این اسکریپت مجموعه‌ای از آزمون‌ها را اجرا می‌کند تا ببیند سیستم شما چه امکاناتی دارد:

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

اگر گزینه‌های سفارشی نیاز دارید (مثلاً فعال کردن پشتیبانی از قابلیت خاص)،
دستور ./configure --help اطلاعات لازم را ارائه می‌دهد. بسیاری از پروژه‌ها فایل INSTALL را هم شامل می‌کنند که گزینه‌های مهم را توضیح می‌دهد.

برنامهٔ make چگونه کار می‌کند؟

make ابزاری است برای مدیریت فرآیند ساخت. این برنامه فایل Makefile را می‌خواند،
در آن فایل هدف‌ها (targets) و وابستگی‌ها تعریف شده‌اند. ساختار کلی یک قاعده در Makefile چنین است:

هدف: فهرستِ-وابستگی‌ها
<TAB> دستوراتی که باید اجرا شوند

به عنوان مثال، فایل Makefile ساده‌ای برای برنامه‌ای به نام hello ممکن است این‌گونه باشد:

hello: hello.o util.o
gcc -o hello hello.o util.o

hello.o: hello.c hello.h
gcc -c -Wall hello.c

util.o: util.c util.h
gcc -c -Wall util.c

clean:
rm -f hello hello.o util.o

وقتی make را بدون آرگومان اجرا کنید، اولین هدف در فایل ساخته می‌شود (در این مثال hello).
اگر تغییری در یکی از فایل‌های منبع ایجاد شود، make با مقایسهٔ زمان تغییر فایل‌ها فقط همان بخش‌های لازم را دوباره کامپایل می‌کند.

هدف‌هایی مانند install یا clean معمولاً دستورات ساده‌ای هستند که ترتیب اجرای عملیات را استاندارد می‌کنند.

سیستم‌های ساخت دیگر

همهٔ پروژه‌ها از Autotools استفاده نمی‌کنند. گاهی در فایل README اشاره می‌شود که باید دستورهای دیگری اجرا کنید:

اصل ماجرا تغییر نمی‌کند: اسکریپت پیکربندی اجرا می‌شود، فایل‌های ساخت ایجاد می‌شوند و در پایان مرحلهٔ نصب انجام می‌گیرد.

مدیریت وابستگی‌ها و مشکلات رایج

هنگام کامپایل احتمالاً با یکی از سناریوهای زیر روبه‌رو می‌شوید:

مستندسازی خطاها و راه‌حل‌ها عادت خوبی است. فایل خروجی configure.log همواره اطلاعات دقیقی از خطاها ارائه می‌دهد.

ردیابی فایل‌های نصب‌شده

از آنجا که make install فایل‌ها را خارج از کنترل مدیر بستهٔ توزیع کپی می‌کند،
بهتر است فهرستی از فایل‌های نصب‌شده داشته باشید. چند روش رایج:

پیگیری نسخه‌ها و مکان نصب‌شان از بروز تداخل جلوگیری می‌کند؛ به‌خصوص اگر برنامه را بعداً به‌روزرسانی یا حذف کنید.

جمع‌بندی


تمرین‌ها

  1. آخرین نسخهٔ برنامهٔ tree یا ncdu را از سایت رسمی دانلود کنید، سپس آن را با گزینهٔ --prefix=$HOME/.local بسازید و نصب کنید.
  2. بسته‌ای را انتخاب کنید که از CMake استفاده می‌کند (برای مثال CMake خود یا neovim). مراحل cmake -S . -B build, cmake --build build و cmake --install build را تمرین کنید.
  3. با استفاده از DESTDIR نصب آزمایشی انجام دهید و فهرستی از فایل‌های نصب‌شده تهیه کنید. این فهرست را برای آگاهی‌های بعدی ذخیره کنید.

مطالعهٔ بیشتر