--- title: راهنمای کوچک GNU Make tags: make category: راهنما uuid: 855ea93b-7af5-4b23-b949-ed398fb9f7d7 --- از زمانی که [تصمیم به ساده‌سازی] گرفته‌ام چند ابزار خیلی به کارم آمده است. یکی از آنها [GNU Make] است که در ادامه شرح می‌دهم. کم و بیش طبق شرح وبسایت خودش: GNU Make ابزاری است که تولید خروجی‌های اجرایی و غیراجرایی از سورس‌های یک برنامه را کنترل می‌کند. اگر سورس برنامه‌ای را دانلود و کامپایل کرده باشید احتمالا به دستوراتی مانند `make` و `make install` برخورد کرده‌اید. لازمه‌ی کار کردن این دستورات هم وجود فایلی بنام `Makefile` در فولدر مربوطه است. در حقیقت Make ابزار ساده ایست که می‌تواند دستوراتی که ما تعریف می‌کنیم اجرا کند. ما به هر گروه از دستورات یک نام اختصاص می‌دهیم و Make آنها را اجرا می‌کند و از خودش چیزی ندارد، حتی دستور `install` فقط یک نام سنتی است و باید توسط برنامه‌ساز تعریف شود. برای استفاده از Make باید تعدادی «قانون» یا Rule داخل فایلی بنام Makefile نوشت. دقت کنید که تورفتگی‌های داخل این فایل حتما باید با TAB باشد. ساختار این فایل اینگونه است: # Rule No 1 target: dependencies ... commands ... هر قانون Make از سه بخش تشکیل شده است: - نام فایل خروجی یا همان target - نام فایل‌های مبداء یا همان dependencyها - دستوراتی برای تولید فایل خروجی از روی فایل‌های مبداء هر Makefile هم می‌تواند تعداد دلخواهی قانون داشته باشد. وقتی در کامندلاین دستور `make` را وارد می‌کنیم اولین قانون داخل فایل اجرا می‌شود، فارغ از نام target. از طرفی هم می‌توان اسم یک قانون را وارد کرد مثلا `make install`، یعنی اجرای قانون `install`. فرض کنید یک فایل C بنام `dorood.c` را می‌خواهیم کامپایل کنیم: ```c #include int main (int argc, char* argv[]) { puts("Hello World!"); return 0; } ``` می‌توانیم Makefile زیر را برای برنامه‌مان بنویسیم: dorood: dorood.c gcc -o dorood dorood.c حالا اگر در کامندلاین `make` یا `make dorood` وارد کنیم این دستورات اجرا می‌شوند: $ make gcc -o dorood dorood.c اما اگر دوباره `make` وارد کنیم: $ make make: 'dorood' is up to date. چه اتفاقی افتاد؟ Make از کجا فهمید که دستور اجرا شده؟ روش کار ساده است. بار اول فایل خروجی بنام `dorood` وجود نداشت پس Make دستور را اجرا کرد. بار دوم اما این فایل ساخته شده بود بنابراین نیازی به اجرای دوباره‌ی دستور نبود. حالا بگذارید فایل `dorood.c` را تغییر بدهیم و دوباره Make را اجرا کنیم. من اینکار را با تغییر آخرین زمان تغییر این فایل به کمک دستور `touch` انجام می‌دهم: $ touch dorood.c $ make gcc -o dorood dorood.c اینبار چه شد؟ Make تاریخ آخرین تغییر خروجی را با ورودی‌ها مقایسه می‌کند. اگر خروجی جدیدتر باشد یعنی همه چیز روبراه است. اگر خروجی قدیمی‌تر از هر یک از وابستگی‌ها باشد یعنی باید دوباره ساخته شود. به این ترتیب Make در وقت کامپایل صرفه‌جویی می‌کند. همه دستورات هم نیاز نیست مروبط به کامپایل باشند. ما می‌توانیم قوانین "phony" داشته باشیم، یعنی «الکی». بیایید یک قانون `clean` اضافه کنیم: .PHONY: clean dorood: dorood.c gcc -o dorood dorood.c clean: rm dorood دستورات «الکی» لزومی ندارد سورس‌کد بخوانند و خروجی بسازند. یعنی Make دنبال فایل ورودی و خروجی برای آنها نمی‌گردد و اگر دستور بدهیم همیشه اجرا می‌شوند. مثلا برای پاک کردن خروجی‌ها دستور زیر کافیست: $ make clean rm dorood قوانین Make را می‌توان به یکدیگر زنجیر کرد. مثلا فرض کنید ما می‌خواهیم فایل بالا را در دو مرحله تولید کنیم. اول سورس را کامپیال می‌کنیم به object file و بعد آن را لینک می‌کنیم و خروجی اجرایی را می‌سازیم. برای این منظور یک قانون جدید نیاز داریم: .PHONY: clean dorood: dorood.o gcc -o dorood dorood.o dorood.o: dorood.c gcc -c dorood.c clean: rm dorood dorood.o و حالا اجرا: $ make clean rm dorood dorood.o $ make gcc -c dorood.c gcc -o dorood dorood.o به صورت سنتی Make برای کامپایل سورس‌کد استفاده شده است، هرچند من از Make در هر پروژه برای تجمیع دستورات پراکنده‌ای استفاده می‌کنم که معمولا باید در هیستوری ترمینالم به دنبال آنها بگردم. مثلا بیایید نگاهی به Makefile ساخت وبسایتم بیندازیم: ```Makefile DBPATH=mehdix.db all: build .PHONY: init serve publish clean init: init_db bundle config set path vendor/bundle bundle install pip install -r scripts/requirements.txt build: comments bundle exec jekyll build comments: @echo rebuilding alef comments python scripts/rebuild_comments.py serve: build bundle exec jekyll serve publish: build rsync -vr _site/* mehdix.ir:/var/www/mehdix.ir/ clean: rm -rf _site rm -rf **/.jekyll-cache init_db: scripts/schema.sql sqlite3 ${DBPATH} < scripts/schema.sql ``` مجال شرح این دستورات نیست، ولی تمام اینها را در دوران پیش از Make در حافظه‌ی ترمینالم جستجو می‌کردم و گاهی فراموش می‌کردم. حالا در ابتدای ساخت هر پروژه همواره یک Makefile می‌سازم که مثل یک اسفنج عمل می‌کند و تمام دستورات ریز و درشتی که به آن پروژه مربوط است را به تدریج به خودش جذب می‌کند. امیدورام مفید فایده شده باشد. [تصمیم به ساده‌سازی]: upcoming-changes.html [GNU Make]: https://www.gnu.org/software/make/