img

ساخت برنامه با استفاده از GNU Make

/
/
/

زمانی که از برای انجام یک پروژه از چند دایرکتوری استفاده می‌کنید، بهتر است چند ترفند را یاد بگیرید.

پروژه‌های بزرگ نرم‌افزاری عمدتا دایرکتوری‌های زیادی را گسترش می‌دهند. پروژه‌ها از این دایرکتوری‌ها برای سازمان‌دهی یک پروژه و یکپارچگی‌ آن استفاده می‌کنند تا توسعه‌دهنده‌ها بتوانند بر روی بخش‌های خاض کار کنند. در برخی از زبان‌ها مانند جاوا، یک نوع از ساختار دایرکتوری به وسیله زبان تقویت می‌شود. در ساخت یک فایل پروژه، ردیابی تمام فایل‌های پروژه دشوار است و توسعه‌دهنده‌ها از چند روش برای سازمان‌دهی فایل‌ها استفاده می‌کنند تا ساختار دایرکتوری پروزه مشخص شود.
ما چند روش رایج را استفاده می‌کنیم. در روش قدیمی سازمان‌دهی برای پروژه‌های دایرکتوری چندگانه باید از makefile جدا در هر دیرکتوری استفاده کرد و این فایل‌ها را به صورت متوالی بررسی کرد، یعنی از خود فرمان به عنوان بخشی از رسید یک قانون استفاده کرد. ما با استفاده از این روش می‌تواینم ساخت را در چند makefile ‌ تقسیم کنیم و هر کدام مسئولیت جدایی در دایرکتوری دارند.
یک ترمینال برای اجرا bash shell: در هر توزیع لینوکس استاندارد است.
ساخت GUI: در تمام توزیع‌های لینوکس موجود است. از آدرس زیر دانلود می‌شود:

https://www.gnu.org/software/make

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

 

ساخت برنامه با استفاده از GNU Make

 

ساخت خودکار دایرکتوری‌ها
در زمان ساخت یک پروژه چند دیرکتوری، تنها فایل‌های منبع در دیرکتوری جدا هستند، خروجی فایل‌های هدف نیز باید به نحوی سازمان‌دهی شوند. بنابراین باید مطمئن شوید زمانی که یک فایل خروجی جدید می‌سازید، دایرکتوری وجود خواهد داشت.
زمانی که این دایرکتوری‌ها را به صورت دستی می‌سازید، آنها را میان فایل‌های مختلف توزیع کنید، اما انجام این کار دشواری است. به علاوه، شاید بهتر باشد کاربر تنظیمات ساختار دایرکتوری را حفظ کند به طوری که بتوانیم از فایل‌های خروجی استفاده کنیم. در این صورت بهتر است، این دایرکتوری‌ها را بسازید.
این کار ساده است، می‌توانیم دایرکتوری را به عنون یک پیش‌نیاز فایل خروجی اضافه کنیم، این موضوع در شکل ۱ نشان داده شده است، قانون Player برنامه تضمین می‌کند که Target‌ (هدف)‌ قبل از جمع‌آوری فایل‌ها و انتقال فایل‌های جمع‌آوری شده به آن دایرکتوری انجام می‌شود. اگر هدف وجود نداشته باشد، درخواست درسید mkdr target را ارسال کرده و آن را بسازید. اما، این رویکرد یک مشکل دارد. برای درک این مشکل باید قانون ویرایش A را به عنوان یک پیش‌نیاز قانون B در دو مورد متفاوت در نظر بگیرید:
۱- مطمئن شوید که هدف برای قانون A، قبل از اجراء رسید قانون B ساخته شده است.
۲- ار فایل مربوط به قانون A قدیمی باشد، اجراء دوباره رسید برای قانون B‌ را هدف قرار دهید.
در رابطه با هدف قانون، ما تنها به مورد ۱ توجه می‌کنیم. یعنی می‌خواهیم مطمئن شویم که هدف mkdir را قبل از جابه‌جایی فایل در آن دیرکتوری اجراء می‌کنیم، یعنی به دنبال ساخت مجدد پلیر در هدف‌های قدیمی نیستیم. در لینوکس، یک مهر زمانی دیرکتوری با تغییر فایل داخل آن دیرکتوری تغییر می‌کند بنابراین اگر یک فایل را در داخل هدف تغییر داده اشیم، باید تمام فایل در آن دیرکتوری را دوباره بسازیم.
خوشبختانه،Make یک مکانیزم را برای پیش‌بینی Makefile ارائه می‌کند که پیش‌نیاز آن باید کارکرد ۱ را برطرف کند، این یک پیش ‌نیاز تنها ترتیب نام دارد، زیرا تنها ترتیب قوانین انجام شده را تحت تأثیر قرار می‌دهد و اگر منسوخ شود، بازسازی را دوباره انجام نمی‌دهد. برای اثبات این‌که پیش‌نیاز در حالت تنها سفارش قرار دارد، ما آن را در سمت راست یک کاراکتر لوله قرار می‌دهیم، مانند

$(TARGET_DIR)/player: player.o dvd_read.o
screen.o | $(TARGET_DIR)
$(CC) -o $@ $^

 

ساخت برنامه با استفاده از GNU Make

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

rule_name : normal prerequisites | order
only

 

Normal و prerequisite (پیش‌نیاز)، پیش‌نیازهای عادی هستند، درحالی کهorder‌ (ترتیب) و only (تنها)، پیش‌نیازهای تنها ترتیب هستند. اگر یک قانون پیش‌نیاز تنها عادی وجود داشته باشد، می‌توانیم یک کاراکتر لوله را حذف کنیم (همان کاری که در آموزش‌ها انجام می‌دهیم.

Calling Make تکراری

در پروژه چند دایرکتوری، ردیابی همه چیز در یک makefile و ساخت همه موارد کار دشواری است. یک الگوی مشترک، ساخت یک makefile جدا در هر دایرکتوری منبع است و باید اعلام کنید که هر makefile درخواست makefile در دیرکتوری‌های فرعی خود را اعلام کند. برای‌مثال فرض کنید که یک برنامه‌ها به چند فایل تصویری در یک دیرکتوری به نام Pictures‌ نیاز داشته باشدو این تصاویر به یک روش براساس کد ساخته می‌شوند. می‌توانید برای توصیف ساخت تصاویر و استفاده از pictures دایرکتوری، یک makefile بسازیم. سپس می‌توانیم makefile زیرارا در یک دایرکتوری ریشه اضافه کنیم. به فرمول زیر توجه کنید:

IMAGE_DIR = «pictures»
TARGET_DIR = «target»
.PHONY: images
images :
cd $(IMAGE_DIR) && $(MAKE)

 

بخش مهم این خط،

cd $ (IMAGE_DIR)&& $ (MAKE)$

است که makefile برای ساخت فایل‌های تصویری را اجراء می‌کند. متغیر MAKE یک متغیر مهم دیگر است که توسط Make‌ استفاده می‌شود و همیشه باید از آن به جای فراخوان اصلی Make استفاده کنید (مانند $ (IMAGE_DIR) && make). یک مزیت اصلی استفاده از $ (MAKE) به جای نوشتن make این است که اگر فایل‌های اجرایی مخلتف به نام make استفاده شوند (برای‌مثال نسخه‌های مختلف از برنامه make)، $ (MAKE) همیشه گسترش پیدا می‌کند و مسیر را به فایل اجرایی می‌دهد. از این مسیر بای فراخوان فایل اصلی استفاده شد. اگر make را با هر پرچم فرمان-خط فراخوان کنیم، در این صورت این موارد به خلاقیت‌های تکراری make منتقل می‌شوند.
یک راه‌حل جایگزین استفاده از cd directory && $(MAKE)، استفاده از پرچم –C برای make‌است که به اجراء makefie در یک دایرکتوری جدا منجر می‌شود. برای‌مثال از فرمول زیر استفاده کنید.

$(MAKE) -C $(IMAGE_DIR)

کاربرد معمولی این ویژگی، اعلام قانون جعلی است (در این حالت تصاویر)، که makefile را در دایرکتوری خرد فراخوان می‌کند. دراین صورت، اگر تصاویر قانون را اجراء کنیم،‌می‌توانیم make، makefile را در دایرکتوری تصاویر فرا بخواند و تصاویر را بسازد.
یک راه دیگر برای انجام این کار، اضافه کردن خود دایرکتوری‌ها به عنوان قوانین جعلی است که با دایرکتوری‌های چندگانه کار می‌کند. همان طور که در شکل ۲ نشان داده شده است، ما یک قانون .build_subdirs داریم که مسئول ساخت تمام دایرکتوری‌های خرد است. این قانون رسید، ندارد اما تمام دایرکتوری‌ها را به عنوان پیش‌نیاز فهرست می‌کند.

 

ساخت برنامه با استفاده از GNU Make ساخت برنامه با استفاده از GNU Make

ساخت متغیرها برای فرمان‌ها
بعضی اوقات در این مقاله ما از متغیر $(CC) برای نمایش c complier cc یا gcc استفاده می‌کنیم. هدف ما این است که ویژگی جابه‌جایی makefile را تا حدامکان افزایش دهیم. ما می‌دانیکه از یک complier استفاده خواهد شد، اما نمی‌توان گفت که کاربر مانند ما همیشه از یک complier استفاده می‌کند. از یک استاندارد مانند gcc_ استفاده می‌شود، بنابراین Make چند متغیر مانند $ (CC) ارائه می‌کند که به صورت خدمکار برنامه مناسب بر روی آن سیستم را ارائه می‌دهد.
ما یک قانون جدا برای هر دایرکتوری خرد داریم که makefile در آن موقعیت را فرا می‌خواند. با این قوانین را نیز .PHONY نام‌گذاری کنیم؛ زیرا در غیر این صورت، Make‌ تصور می‌کند که آنها به خود دایرکتوری‌ها اشاره دارند و آنها را اجراء نمی‌کند (زیرا از نظر makefile، دایرکتوری‌ها قدیمی نیستند).

 

انتقال متغیرها میان نمونه‌ها
با استفاده از یک makefile پیچیده ما Make را چندین را فراخواندیم، و می‌توانیم ارزش‌ها میان این مغیرها را به نوآوری‌های مخلتف Make منتقل کنیم.
برای‌مثال می‌توانیم از تصاویر در پوشه Pictures در پروژه خود استفاده کنیم و لازم نیست متغیر IMAGE_DIR را در تمام makefile تعیین کنیم. متاسفانه، اگر IMAGE_DIR را در داخل یک makefile تعریف کنیم، دیگر نمی‌توان در جای دیگر از آن استفاده کرد.
برای حل این مشکل باید توجه داشت که ارتباط نزدیکی میان متغیر Make و متغیرهای محیط وجود دارد. Make از این متغیرها برای اجراء در shell استفاده می‌کند. Make متغیر خود را با همین نام می‌سازد. بنابراین می‌توان با معرفی متغیرهای در دسترس ‌makefile در shell، قبل از اجراء این مشکل را حل می‌کنیم.

$ export IMAGE_DIR=pictures
$ make

 

سپس می‌توانیم با استفاده از $ (IAMGE_DIR) ‌در داخل makefile ‌ به متغی دسترسی داشته باشیم. اما قصد داریم یک راه‌حل Make-only‌ را ایجاد کنیم، به خصوص آنکه به دنبال تغییر ارزش متغیر در makefile ‌ هستیم، می‌توانیم این متغیر را با استفاده از کارکرد‌های Make-specific مانند patsubst بسازیم. خوشبختانه، Make به ما کمک می‌کند تا یک متغیر را در داخل متغیر محیط بسیازیم. برای این منظور از کیبرد export استفاده می شود.

export IMAGE_DIR = pictures

 

در نگارش این متن، تضمین می‌کنیم که متغیر IMAGE_DIR برای تمام خلاقیت‌ها Make‌ در دسترس است. می‌توانیم با استفاده از export کیبورد، دامنه متغیر Make‌ را تغییر دهیم.

TARGET_DIR = target
export TARGET_DIR
# Accessible to all invocations of Make
unexport TARGET_DIR
# Can only be accessed within this makefile

 

استفاده از shell‌ برای حلقه‌ها
برخی ازMakefileهای ساخته‌شده در دنیای واقعی مشاهده می‌شوند. نمونه‌ای از آن در شکل ۳ نشان داده شده است که از یک shell‌برای حلقه و فراخوان Makefile در تعداد دایرکتوری‌های خرد استفاده می‌کند. یعنی آنها قانونی دارند که از حلقه shell باری تکرار فهرست دایرکتوری‌های خرد استفاده می‌کنند و هر بار $ (MAKE) را فرا می‌خوانند. رویکرد توصیف‌شده بهتر است، چرا که برای‌مثال از اجراء موازی استفاده می‌کند.
این رویکرد «makefile چندگانه» یک روش تفکیک فرایند ساخت در بخش‌های مختلف است. اگر بخش‌های مختلف یک پروژه جدا شوند، کار به خوبی پیش‌ می‌رود. اما، جداسازی بخش‌های ساخت با استفاده از تفکیک makefileها چند مشکل به همراه دارد.
مشکل اصلی این است که فراخوان‌های مختلف Make به طول کامل جدا هستند. آنها متغیر‌های مشتری دارند که در بخش قبلی مشاهده کردید، اما الگو‌ی آنها مشترک نیست. به طور خاص می‌توانیم یک قانون از makefile را به مستقل از دیگری تعیین کنیم.
برای تمام متغیرهای محیطی در Shell، متغیرهایی با نام مشابه بسازید.
باری درک مشکل، به مثال تصویر برگردید. فرض کنید که یک برنامه به نام gamepad را ساخته‌اید که فایل‌های عنوان نیاز دارد. این فایل‌ها عبارتند از ArrowButton.h‌ و Scrren.h. این فایل‌های عنوان در یک دایرکتوری include.‌قرار دارند و به وسیله انسان‌ها نوشته نمی‌شوند، بلکه به صورت خودکار در فرایند‌های جدا ایجاد می‌شوند. این فرایند در داخل یک فایل‌جدا انجام می‌شود که در دایرکتوری include‌ قرار دارد.
باید فایل‌های تصویری را به عنوان موارد وابسته اضافه کنیم:

.PHONY: include
include :
$(MAKE) -C $@
gamepad : gamepad.c ArrowButton.h Screen.h
gcc -o gamepad.c

 

ArrowButton.h‌ و Screen.h به وسیله یک makefile در داخل دایرکتوری include‌ ساخته می‌شوند، آنها به عنوان قوانین در این makefile فهرست نشده‌اند. این یعنی آنها همانند فایل‌های منبع هستند و در صورتی که این فایل‌ها نباشند، Make کار را متوقف می‌کند. تا زمانی که قبل از اجراء make gamepad، make include ‌ را اجراء کنید، همه چیز خوب پیش‌ می‌رود. برای پیشرفت کار، بهتر است یک قانون را اضافه کنید که تضمین می‌کند فایل‌های داخل دایرکتوری include همیشه با استفاده از makefile در داخل آن دایرکتوری ساخته می‌شوند.

include/* :
$(MAKE) -C include $@

 

مشکل این جاست که makefile کنونی ما به پیش‌نیازها برای‌مثال screen.h در داخل Makefile دوم اشاره دارد. البته، میتوانیم برای توصیف این موضوع، اطلاعات بیشتری را در makefile ‌ اول اضافه کنیم، بعد مقدار زیادی از کد تکراری داریم و مزایای استفاده از Makefile چندگانه اولیه را از دست می‌دهیم.
(جعلی)، می‌توانیم تضمین کنیم که آنها همیشه اجراء می‌شوند، حتی اگر نام مشترکی با دایرکتوری داشته باشند و این نام جدید باشد.
تنها رویکرد واقعی برای این مشکل، استفاده از یک فراخوان برای Make است و نباید از آن را به صورت مکرر فراخواند. خوشبختانه این رویکرد ما را به استفاده از یک Makefile بزرگ مجبور نمی‌کند، اما باید از makefile در داخل یکدیگر و از include استفاده کنیم.
این یک استراتژی برای کار با پروژه‌های بزرگ و چند دایرکتوری است. همچنان می‌تتوانید یک makefile ‌را در هر دایرکتوری قرار دهیم، اما باید این makefileها را در Makefile پایه استفاده کنیم و لازم نیست آنها را به صورت مکرر فرا بخوانیم. برای‌مثال، کد زیر یک makefile را در دایرکتوری‌های pictures و include در makefule اضافه می‌کنیم.

include pictures/makefile
include include/makefile

 

در استفاده از این رویکرد، ماید مطمئن شویم که هیچ‌کدام از نام‌های متغیر‌ها در makefile include، با هیچ کدام از نام‌های متغیر‌ها در makefile‌ اصلی همخوانی ندارند، چرا که include فقط متن را به دایرکتوری مورد نظر اضافه می‌کند.

 

قوانین ساخت خودکار
فرض کنید یک برنامه C‌ بزرگ را می‌سازید. قوانین زیادی برای این کار وجود دارد.

component.o: component.c some.h header.h files.h

 

جایی که با استفاده از #include فایل‌های عنوان در ‌component.c استفاده می‌شوند. می‌توانیم فایل‌های .h‌ را اضافه کنیم و این فایل .c را همیشه بسازیم، اما بهتر است هر بار makefile‌ را تغییر ندهیم.
خوشبختانه gcc complier از پرچم‌های فرمان-خط استفاده می‌کند که به ما اجازه می دهد تا به صورت خودکار فهرست پیش‌نیاز Make را برای فایل .c‌ مورد نظر تولید کنیم.

$ gcc -MM -MG player.c
player.o: player.c player.h screen.h dvd
read.h

 

خوشبختانه می‌توانیم خروجی این دستور را به فایل تبدیل کنیم و سپس با یک کیبرد include آن را در makefile قرار دهیم. حتی می‌توانیم با استفاده از خود makefile، این فرایند را خودکار کنیم.
نام سنتی برای این وارد‌ «makefile ‌ وابستگی» است که با جایگزینی .c بر روی پایان نام فایل با .d ایجاد می‌شود. بنابراین می‌توان از نقش الگو استفاده کرد.

%.d : %.c

$(CC) -MM -MG $(CFLAGS) $* > $@

 

این کد فرمان cc –MM –MG (و گزیه‌های دیگر برای gcc تعیین‌شده در متغیر CFLAGS)را بر روی یک فایل .c‌ مورد نظر می‌سازد و فایل d. را تولید می‌کند که حاوی قانون و فهرست مناسب پیش‌نیاز است. برای‌مثال، اجرا make player.d یک فایل player.d را می‌سازد که حاوی player.o: player.c player.h screen.h dvd_read.h است. می‌توانیم از تمام فایل‌های d به صورت همزمان استفاده کنیم.

SOURCES = player.c screen.c dvd_read.c
include $(SOURCES:.c=.d)

 

این موضوع رفتار خاص کیبرد include را نشان می‌دهد، اگر نتوانید فایل include را پیدا کنید، با استفاده از قانون مناسب می‌توانیم آن را بسازیم. استفاده از این فایل‌های .d، یک فهرست از پیش‌نیاز‌ها را برای فایل‌های .o وارد می‌کند، این فایل برای دریافت‌کننده ساخته نمی‌شود.
ما باید این فایل را خودمان ارائه کنیم یا اجازه دهیم Make به صورت خودکار استدلال را انجام دهد.
این همان چیزی است که ما می‌خواهیم، در صورتی که یکی از فایل‌های .d تغییر کند، باید کاری کنیم کهMake فایل‌های .d را بازسازی کنیم. برای این منظور، محتویات فایل‌های .d را تغییر دهید، به صورتی که این فایل‌ها از خود به عنوان اهداف استفاده کنند. یعنی، به جای قانون player.o: player.c… از قانون زیر استفاده کنید:

player.o player.d : player.c player.h screen.h
dvd_read.h

یک راه معمول برای انجام این کار، استفاده از فرمان sed برای تبدیل خروجی از gcc به شکل مورد نظر ما است. برای‌مثال به کد زیر توجه کنید:

%.d : %.c
$(CC) -MM -MG $(CFLAGS) $* |
sed -E ‹s/(.*)\.o/\1\.o \1\.d/› > $@

 

این فعالیت تا زمانی کارایی دارد که فایل‌های ما در دایرکتوری قرار داشته باشند. اگر این اتفاق نیافتد، تنها راه ممکن این است که از gcc به صورت خودکار یک قانون را تولید کند. این قانون فرض می‌کند که فایل .o در دایرکتوری فایل‌های .c‌ قرار دارند، باید فرمان sed را کمی تغییر دهیم:
از کد زیر استفاده کنید:

sed -E ‹s/(.*)\.o/›$(dirname $*)›\/\۱\.o
‹$(dirname$*)›\/\۱\.d›

یا باید به مورد مشابهی برسیم. با استفاده از روش‌ها، پروژه‌های چند دایرکتوری آسان‌تر مدیریت می‌شوند.

نظر بدهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

It is main inner container footer text