آشنایی با نحوه پورت کردن برنامه‌ها در FreeBSD

تاریخچه FreeBSD

پورت‌های FreeBSD شامل هزاران بسته نرم‌افزاری است که برای اجرا بر روی این سیستم‌عامل آماده شده‌اند. هر کسی می‌تواند برنامه جدیدی را برای FreeBSD پورت کرده یا همین طور برای مدیریت کردن پورت های موجود داوطلب شود.

به طور کلی می‌توان گفت مراحل زیر در هنگام نضب یک پورت انجام می‌پذیرد:

  1. دانلود کد‌های منبع به صورت یک آرشیو فشرده شده
  2. بررسی صحت فایل‌های دانلود شده
  3. استراج کدهای منبع
  4. اعمال کردن تغییرات و همین طور وصله های مورد نیاز
  5. پیکر‌بندی برنامه برای انجام عمل کامپایل
  6. کامپایل برنامه
  7. نصب برنامه درسیستم
  8. حذف برنامه

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

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

# New ports collection makefile for:   oneko
# Date created:        5 December 1994
# Whom:                asami
#
# $FreeBSD$
#
PORTNAME=      oneko
PORTVERSION=   1.1b
CATEGORIES=    games
MASTER_SITES=  ftp://ftp.cs.columbia.edu/archives/X11R5/contrib/
MAINTAINER=    asami@FreeBSD.org
COMMENT=       A cat chasing a mouse all over the screen
MAN1=          oneko.1
MANCOMPRESSED= yes
USE_IMAKE=     yes
.include <bsd.port.mk>

اگر محتویات این فایل برای شما غیر قابل فهم است نگران نباشید، بیشتر قسمت‌های مقاله درباره همین فایل بحث می‌کنند!

فایل بالا شامل متغیر‌هایی است که تعدادی از آنها را در اینجا خیلی کوتاه بررسی می‌کنیم (بعدا آنها را به طور مفصل و با جزئیات بررسی خواهیم کرد):

PORTNAME: این متغیر نام پورت مورد نظر را مشخص می‌کند و باید حتماً استفاده شود.

PORTVERSION: نسخه یا ورژن پورت را مشخص می‌کند. نسخه پورت نباید حاوی کاراکتر خط تیره (-) باشد. این متغیر اجباری است.

MASTER_SITES: این متغیر مشخص می کند که کد‌های منبع و فایل‌های برنامه باید از کجا دانلود شوند. می‌توانید چند آدرس مختلف را مشخص کنید که با کاراکتر خط فاصله از هم جدا شده‌اند.

 

فایل توضیحات

هر پورت دو فایل به نام‌های pkg-descr و pkg-plist دارد.

فایل pkg-descr

این فایل دربرگیرنده توضیحاتی درباره پورت مورد نظر است. مثلاً برنامه برای انجام چه کاری نوشته شده، چه قابلیت‌هایی دارد، صفحه خانگی آن چیست و … .

یک نمونه از این فایل را در زیر مشاهده می‌کنید:

This is a port of oneko, in which a cat chases a poor mouse all over
the screen.
 :
(etc.)
WWW: http://www.oneko.org/

فایل pkg-plist

این فایل دربرگیرنده لیست تمام فایل‌هایی است که توسط این پورت بر روی سیستم نصب می‌شوند. همیچنین اصطلاح packing list هم به این فایل اطلاق می‌شود. چون بسته‌های باینری از روی محتویات این فایل ایجاد می‌شوند. تمام مسیر‌های موجود در این فایل وابسته به مقدار متغیر ‎${PREFIX} هستند. (مقدار این متغیر معمولاً /usr/local یا /usr/X11R6 است) توجه داشته باشید که man page هایی که توسط برنامه در سیستم نصب می‌شوند نباید در این فایل لیست شوند. اگر برنامه در هنگام نصب تعدادی دایرکتوری‌ در سیستم ایجاد می‌کند، علاوه بر اینکه این دایرکتوری‌ها را لیست می‌کنید، عبارت ‎@dirrm‎ را هم در ابتدای خطوط قرار دهید تا در هنگام حذف برنامه، دایرکتوری‌های مرتبط با آن هم پاک شوند. یک نمونه از این فایل را در زیر مشاهده می‌کنید:

bin/oneko
lib/X11/app-defaults/Oneko
lib/X11/oneko/cat1.xpm
lib/X11/oneko/cat2.xpm
lib/X11/oneko/mouse.xpm
@dirrm lib/X11/oneko
نکته:
پیشنهاد می‌کنیم که لیست فایل‌ها را بر اساس حروف الفبا مرتب کنید تا بعداً در هنگام به روز‌رسانی پورت کارتان راحت‌تر شود.
نکته:
ایجاد فایل pkg-plist به صورت دستی می‌تواند بسیار وقت گیر و خسته‌کننده باشد؛ مخصوصاً اگر برنامه تعداد زیادی فایل در سیستم نصب می‌کند. یک روش خودکار برای ایجاد این فایل وجود دارد که بعداً درباره آن صحبت خواهیم کرد.

تمام پورت ها باید این فایل را داشته باشند. اما فقط در یک مورد می‌توان از آن صرف نظر کرد. اگر برنامه تعداد مشخص و اندکی فایل در سیستم نصب می‌کند، می توان لیست فایل‌ها و دایرکتوری‌ها را با استفاده از متغیر‌های PLIS_FILES و PLIST_DIRS در Makefile مشخص کرد. به عنوان مثال می توانیم با قرار دادن خطوط زیر در Makefile از ایجاد فایل pkg-plist خود‌داری کنیم:

PLIST_FILES=    bin/oneko \
                lib/X11/app-defaults/Oneko \
                lib/X11/oneko/cat1.xpm \
                lib/X11/oneko/cat2.xpm \
                lib/X11/oneko/mouse.xpm
PLIST_DIRS=     lib/X11/oneko

طبیعتا اگر برنامه هیچ دایرکتوری در سیستم ایجاد نمی‌کند می توان متغیر PKG_DIRS را بدون مقداردهی رها کرد.

بررسی صحت فایل‌های دانلود شده

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

اگر فایل‌های برنامه به صورت مکرر تغییر می‌کنند و شما مطمئن هستید که آنها را از جای مطمئنی دریافت می‌کنید، می توانید با استفاده از متغیر IGNOREFILES این فایل‌ها را نادیده گرفته تا هیچ بررسی جهت اطلاع از صحت آنها صورت نگیرد. در این صورت Checksum آن فایل در هنگام اجرای دستور make makesum محاسبه نخواهد شد.

آزمایش پورت

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

  • pkg-plist تمام فایل‌ها و دایرکتوری‌هایی که توسط پورت در سیستم نصب می‌شوند را دربرگیرد. همچنین هیچ فایلی از قلم نیفتد و فایل‌های اضافه و فایل‌هایی که به پورت مرتبط نیستند هم در لیست وجود نداشته باشند.
  • چک کنید که پورت مورد نظر هیچ اشکالی با دستور reinstall نداشته باشد و بتوان آن را چندین بار از طریق دستور reinstall نصب کرد.

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

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

make install
make package
make deinstall
pkg_add package-name
make deinstall
make reinstall
make package

مطمئن شوید که در مراحل package و deinstall هیچ هشداری بر روی صفحه نمایش چاپ نمی‌شود. بعد از مرحله سوم چک کنید که تمام فایل‌ها و دایرکتوری‌ها از روی سیستم حذف شده باشند. بعد از مرحله چهارم برنامه را تست کرده و ببینید آیا وقتی که برنامه از روی یک بسته باینری هم نصب می‌شود به درستی کار می‌کند یا نه.

ارسال پورت

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

بعد از انجام این کار خروجی را با استفاده از send-pr یا معادل تحت وب آن ارسال کنید. برای انجام این کار، در قسمت Category گزینه ports و در قسمت class هم گزینه change-request را انتخاب کنید. همین طور در فیلد Description توضیح مختصری درباره پورت خود بنویسید و محتویات فایل shar را هم در فیلد Fix وارد کنید.

نکته:

شما می‌توانید با رعایت چند نکته کوچک کار توسعه دهندگان را راحت‌تر کنید. مثلاً اگر پورت جدیدی را ارسال می‌کنید، بهتر است فیلد Synopsis را در قالب زیر پر کنید:

New port: <category>/<portname> <short description of the port>

یا اگر پورتی که قبلاً موجود بوده را به روز‌رسانی کرده‌اید، اطلاعات فیلد Synopsis را در قالب زیر وارد کنید:

Update port: <category>/<portname> <short description of the update>

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

یک بار دیگر متذکر می‌شویم که کدهای منبع، دایرکتوری work و بسته‌باینری را ارسال نکنید.

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

پس از اینکه توسعه دهندگان پورت شما را بررسی کردند، نکاتی که احتمالاً باید بدانید را برای شما ارسال کرده و سپس آن را در درخت پورت ها قرار می‌دهند. همچنین نام شما هم در لیست دیگر مشارکت‌کنندگان پروژه FreeBSD اضافه خواهد شد.

بررسی جزئیات بیشتر

در ادامه خواهیم دید که وقتی کاربر دستور make را اجرا می‌کند چه اتفاقی می‌افتد.

دستور fetch اجرا می‌شود. دستور fetch دایرکتوری DISTDIR را چک کرده و مطمئن می‌شود که آیا فایل‌های مورد نیاز برای ساخت پورت وجود دارند یا نه؟ اگر فایل‌ها در دایرکتوری DISTDIR وجود نداشتند، آنها را از آدرس MASTER_SITES دریافت خواهد کرد. متغیر MASTER_SITES در Makefile تعریف می‌شود و دربرگیرنده لیستی از سایت‌هایی است که می‌توان فایل‌های برنامه را از آنجا دریافت کرد. علاوه بر این سایت‌ها، فایل‌های برنامه از مسیر ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/distfiles هم قابل دریافت هستند چون همیشه برای اطمینان یک نسخه از آنها در این دایرکتوری نگه‌داری می‌شود. سپس فایل‌ها با استفاده از برنامه‌ای که توسط متغیر FETCH مشخص شده، دانلود می‌شوند و در دایرکتوری DISTDIR قرار می‌گیرند.

دستور extract اجرا خواهد شد. این دستور کد‌های منبع برنامه را از دایرکتوری DISTDIR برداشته و آنها را در یک جای موقت (که توسط WRKDIR مشخص می‌شود) استخراج می‌کند. کد‌های منبع معمولاً فایل‌هایی با پسوند .tar.gz هستند و معمولاً در یک دایرکتوری به نام work استخراج می‌شوند.

دستور patch اجرا می‌شود. در ابتدا تمام patch هایی که توسط متغیر PATCHFILES مشخص شده‌اند بر روی کد‌های منبع اعمال می‌شوند. سپس تمام فایل‌هایی که patch-* نام دارند و در دایرکتوری PATCHDIR قرار گرفته‌اند به ترتیب حروف الفبا بر روی کد‌های منبع اعمال خواهند شد. مقدار پیش فرض دایرکتوری PATCHDIR بر روی files تنظیم شده است.

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

  1. در صورت وجود، اسکریپتی که در مسیر scripts/configure قرار دارد اجرا خواهد شد.
  2. اگر یکی از متغیر‌های HAS_CONFIGURE یا GNU_CONFIGURE تعریف شده باشند، اسکریپت WRKSRC/configure اجرا خواهد شد.
  3. اگر متغیر USE_IMAKE تعریف شده باشد، دستور XMKMF اجرا می‌شود (پیش فرض آن xmkmf -a است)

دستور build اجرا می‌شود. این دستور با استفاده از Makefile ای که در دایرکتوری کد‌های منبع وجود دارد، برنامه را کامپایل خواهد کد. در صورتی‌که متغیر USE_GMAKE تعریف شده باشد، از برنامه GNU make استفاده خواهد شد. در غیر این صورت از make استفاده می‌شود.

آنچه که با هم مرور کردیم عملیات پیش فرض بعد از اجرای دستور make بود. اگر این عملیات برای شما کافی نیست نگران نباشید. شما می‌توانید در Makefile دستوراتی به نام pre-something یا post-something تعریف کنید. مثلاً با تعریف دستور pre-configure می‌توانید عملیاتی را قبل از اجرای configure انجام دهید و به همین ترتیب حتی می‌توانید اسکریپت‌هایی با همین نام‌ها را در دایرکتوری scripts قرار دهید تا عملیات دلخواه خود را قبل یا بعد از اجرای عملیات‌های پیش فرض به انجام برسانید.

به عنوان مثالی دیگر، اگر در Makefile خود post-extract را تعریف کرده باشید، و فایلی به نام pre-build هم در دایرکتوری scripts قرار داشته باشد، بعد از اینکه فایل‌ها استخراج شدند، عملیات تعریف شده توسط post-extract اجرا شده و همین طور قبل از انجام عمل build، اسکریپت pre-build اجرا می‌شود.

عملیات‌های پیش فرض در فایل bsd.port.mk و در قالب do-something تعریف شده‌اند. مثلاً عملیات مربوط به دستور extract به صورت do-extract تعریف شده و به همین ترتیب. البته شما خودتان هم می‌توانید این عملیات‌ها را در Makefile پورت خود تنظیم کنید تا عملیات‌های پیش فرض را بی اثر کنید.

دریافت کد‌های منبع

این مرحله شامل دریافت کد‌های منبع یک برنامه (به صورت یک آرشیو فشرده شده مثل foo.tar.gz یا foo.tar.Z) و قرار دادن آنها در DISTDIR می‌شود.

شما باید وب سایت(های) اصلی برای دریافتِ کدهای منبع را با متغیر MASTER_SITES مشخص کنید. همان طور که ممکن است بدانید سایت هایی مانند Sourceforge و Github وجود دارند که میزبانی هزاران پروژه نرم‌افزاری را بر عهده دارند. تعدادی ماکرو برای استفاده راحت‌تر از این سایت ها در فایل bsd.sites.mk تعریف شده که می‌توانید از آنها استفاده کنید. نحوه استفاده از این ماکرو‌ها بعداً شرح داده خواهد شد. لطفاً تا جایی که ممکن است از این ماکرو‌ها استفاده کنید.

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

تغییر دادن پورت

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

وصله‌ها یا patchها

به طور خلاصه، اگر فایلی نیاز به تغییر دارد، یک نسخه از آن را با پسوند .orig کپی کرده و سپس فایل اصلی را ویرایش کنید. در نهایت دستور make makepatch را اجرا کنید. برای مثال اگر فایلی به نام filename.c احتیاج به تغییر داشته باشد، ابتدا یک نسخه از آن را با پسنود .orig کپی می‌کنیم:

cp filename.c filename.c.orig

سپس تمام تغییرات مورد نظر خود را در filename.c اعمال کرده و سپس این دستور را اجرا می‌کنیم:

make makepatch

در هنگام آماده کردن یک پورت، می توانید تغییراتی که در فایل‌ها ایجاد می‌کنید را با diff ضبط کرده تا بعداً بتوانید آنها را مجدداً به صورت خودکار با استفاده از patch بر روی کد‌های منبع اعمال کنید. هر فایل patch ای که می‌خواهید بر روی کدهای منبع اعمال کنید، باید نامی در قالب patch-* داشته باشد که * مسیر فایلی است که patch باید بر روی آن اعمال شود. همانند patch-Imakefile یا مثلاً patch-src-config.h. این فایل‌ها باید در داخل دایرکتوری PATCHDIR قرار بگیرند. مقدار پیش فرض این دایرکتیو files است که در همان دایرکتوری پورت در کنار فایل‌های Makefile و pkg-plist قرار دارد. تمام patch ها باید وابسته به مسیر WRKSRC باشند (همان دایرکتوری که پورت شما خودش را در آن استخراج می‌کند)

لطفاً فقط از کاراکتر‌های [-+._a-zA-Z0-9] برای نام گذاری فایل‌های patch استفاده کنید.

اگر می‌خواهید از تمام فایل‌های موجود در یک دایرکتوری patch تهیه کنید، میتوانید از گزینه ‎-r‎ در دستور diff استفاده کنید. اما بعد از انجام این کار حتماً نتیجه را چک کرده تا مطمئن شوید که هیچ چیز اضافه و بدرد‌نخوری در خروجی قرار نداشته باشد.

اگر احتیاج به حذف کردن فایل‌(هایی) از کد‌های منبع دارید، می توانید این کار را در مرحله post-extract انجام دهید.

اگر مشکل شما تنها با چند جایگزینی ساده حل می‌شود، می‌توانید این کار را مستقیماً از طریق Makefile و با استفاده از sed انجام دهید. در مواقعی که می‌خواهید مقادیر یک متغیر را تغییر دهید، این روش می‌تواند بسیار مفید واقع شود. برای مثال:

post-patch:
    @${REINPLACE_CMD} -e 's|for Linux|for FreeBSD|g' ${WRKSRC}/README
    @${REINPLACE_CMD} -e 's|-pthread|${PTHREAD_LIBS}|' ${WRKSRC}/configure

اگر برنامه در محیط ویندوز نوشته شده باشد، انتهای خطوط در فایل‌های متنی با کاراکتر ‎CR/LF مشخص می‌شود. اما در یونیکس انتها فایل‌ها فقط با کاراکتر LF مشخص می‌شود که این ناسازگاری سبب بروز اشکالاتی در هنگام اعمال patchها، کامپایل کردن برنامه و همین طور اجرای اسکریپت‌ها می‌شود. برای تبدیل کاراکتر خط جدید به LF می‌توانید به صورت زیر عمل کنید:

USE_DOS2UNIX=    yes
DOS2UNIX_REGEX=  .*\.(c|cpp|h)

مرحله پیکربندی پورت

تمام تغییرات لازم را در اسکریپت configure انجام داده و آن را در زیردایرکتوری scripts ذخیره کنید. همان طور که قبلاً گفته شد، می‌توانید در داخل Makefile و با استفاده از عملیات pre-configure و post-configure انجام دهید.

اگر برنامه شما یک اسکریپت configure دارد، باید خط زیر را به Makefile اضافه کنید تا این اسکریپت اجرا شود:

HAS_CONFIGURE=  yes

ممکن است اسکریپت configure به برنامه GNU Make و دیگر برنامه‌های مرتبط احتیاج داشته باشد. در این صورت به جای خط بالا باید خط زیر را اضافه کنید:

 GNU_CONFIGURE=  yes

پیکربندی Makefile

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

فایل فشرده شده کد‌ منبع

کد‌های منبع به صورت یک آرشیو فشرده شده منتشر می‌شوند که در فرهنگ اصطلاحات FreeBSD به آن‌ها distfile گفته می‌شود. اگر distfile پورت شما نامی در قالب foozolix-1.2.tar.gz دارد، می توانید از مطالعه این قسمت صرف نظر کنید. در غیر این صورت شما باید متغیر‌های DISTVERSION ،DISTNAME، EXTRACT_CMD ،EXTRACT_BEFORE_ARGS ،EXTRACT_AFTER_ARGS، EXTRACT_SUFX ،DISTFILES را مطابق با نیاز خود تنظیم کنید. در بد‌ترین حالت ممکن است مجبور باشید عملیات do-extract را مجدداً و با توجه به نیاز خود تعریف کنید.

نام‌گذاری

اولین قسمت هر MAkefile، در برگیرنده اطلاعات اساسی درباره پورت مورد نظر است. مانند نام پورت، نسخه پورت و دسته‌ای که پورت در آن قرار دارد.

همان طور که قبلاً گفته شد، شما باید نام پورت را با استفاده از متغیر PORTNAME و نسخه و ورژن آن را با متغیر PORTVERSION مشخص کنید.

متغیر EXTRACT_SUFX پسوند distfile را مشخص می‌کند و به صورت پیش فرض مقدار tar.gz برای آن درنظر گرفته شده است. اگر پسوند distfile شما چیز دیگری است، می توانید آن را با استفاده از همین متغیر مشخص کنید. مثلاً برای پسوند .tar.bz2 می‌توانید به صورت زیر عمل کنید:

USE_BZIP2=	yes

متغیر USE_BZIP2 هم تعیین می‌کند که باید از برنامه bzip2 برای استخراج پورت استفاده شود.

همین طور برای فایل‌های .zip:

USE_ZIP=	yes

برای مدیریت و دسترسی راحت‌تر پورت ها را با توجه به کاربردشان رده‌بندی می‌کنند. شما هم باید رده مناسبی را برای پورت خود انتخاب کنید. سپس با استفاده از متغیر CATEGORIES دسته مورد نظر خود را مشخص کنید.

قسمت دوم هر Makefile دربرگیرنده اطلاعاتی در مورد فایل‌هایی است که باید دانلود شوند و با استفاده از آن پورت را کامپایل کرد که این مورد شامل کد‌های منبع، patchها و … می‌شود.

اگر distfile شما نامی دارد که از قالب رایج تبعیت نمی‌کند، می توانید با متغیر DISTNAME نام آن را مشخص کنید.