مستندات خودکار برای Makefile

این روزها در تمام پروژه هامون از Makefile استفاده می کنیم. ایجاد یک راهنما و مستند ساده برای این فایل میتونه در توسعه اش بسیار موثر باشه.

مستندات خودکار برای Makefile

شرح ماجرا

همه پروژه های ما حاوی یک Makefile هستند تا مراحل نصب ٬ تست و استقرار برای کاربران ساده بشه. اکثر اسامی که برای target ها در نظر گرفته میشن طبق استاندارد های جا افتاده نوشته میشن. برای مثال build, test یا run اما برخی از آنها واقعا نیاز به توضیح اضافه دارند.

برای مثال به این موارد نگاه کنید :

make init
make js
make restart-service
make run-api
make run-dev

شاید در نگاه اول برای خودتان که توسعه دهنده پروژه هستید این target ها معنا دار باشند ولی به دید فردی که برای اولین بار با این پروژه مواجه میشه شاید هیچکدوم این ها دقیق منظورشون رو نشون ندهند. هرچه target های اختصاصی بیشتری در یک پروژه وجود داشته باشه باید توضیحات مناسبی هم برای اون ها در نظر بگیریم.

معمولا چنین کاری در فایل README پروژه انجام میشه:

نمونه یک Readme

اما وقتی از CLI استفاده می کنیم ترجیح اینه که از ابزار های self-documentation بهره ببریم. آیا بهتر نیست فقط با تایپ دستور make لیستی از کلیه دستور ها همراه با توضیحات اون ها لیست بشه ؟ چیزی شبیه به این عکس:

خروجی مطلوب

راه حل

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

برای شروع باید برای هر target یک توضیح بنویسیم. مسلما باید از کامنت گذاری استفاده کنید. در Makefile کامنت ها با علامت # شروع میشن. برای مثال:

# Build the project
build: # info
    go build

ولی باید تفاوتی بین نظرات عادی و نظرات مربوط به مستندات ایجاد کنیم. برای این کار پیشنهاد میکنم از دو کارکتر ## استفاده کنید. برای مثال:

install: ## Install JavaScript dependencies
    @echo "Installing dependencies"
    @npm install

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

JS_DST := nikas/js/embed.min.js nikas/js/embed.dev.js \
            nikas/js/count.min.js nikas/js/count.dev.js \
            nikas/js/count.dev.js.map nikas/js/embed.dev.js.map

init: ## Install JavaScript modules
    npm install -f

nikas/js/%.min.js: $(JS_SRC)
    npm run build-prod

nikas/js/%.dev.js: $(JS_SRC)
    npm run build-dev

js: $(JS_DST) ## Build the frontend

اگر دقت کنید فقط برای دستورات init و js توضیحاتی وجود داره چون فقط همین target ها هستند که به صورت مستقل اجرا میشن.

به باقی موارد internal target گفته میشه. این target ها برای استفاده داخلی هستند و به کاربر نمایش داده نمیشن.

ساخت مستندات

حال با در نظر گرفتن شرایط فوق ٬ توسط چندتا دستور میتونیم خروجی دلنشین و زیبایی داشته باشیم.

ابتدا باید کل خط های این فایل که مورد نظر ما است استخراج بشه. درواقع هر خطی که با یک عبارت و کارکتر : شروع بشه ٬ در ادامه ## داشته باشه و کلماتی هم بعدش باشن. برای این کار میتونیم از دستور grep استفاده کنیم:

grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile

این دستور دقیقا توضیحی که بالاتر دادم رو به شما میده:

init: ## Install JavaScript modules
js: $(JS_DST) ## Build the frontend

حال که چنین خروجی داریم با استفاده از awk میشه دستورات و توضیحات مربوط به اون ها رو از همدیگه جدا کرد و در یک قالب بهتر نمایش داد. به این دستور دقت کنید:

awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

در واقع از ## به عنوان یک delimiter استفاده می کنیم و عنوان از توضیحات جدا میشه. سپس با استفاده از printf هر قالب خاصی که مد نظرمون باشه به همراه رنگ بندی دلخواه اعمال می کنیم. باید توجه داشته باشید که این دستورات مکمل یکدیگر هستند. پس دستور نهایی ما چیزی شبیه به این خواهد بود:

grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

در نهایت یک target برای این دستور به فایل خودتون اضافه کنید:

help: ## Show this help.
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

حالا با استفاده از دستور make help میتونید خروجی مستندات رو ببینید.

نکات کنکوری و اضافه کاری

میتونیم دستی به سرش بکشیم و بر اساس حروف الفبا مرتبش کنیم. اینطوری پیدا کردن دستورات و توضیحات اون ها ساده تر میشه:

help: ## Show this help.
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

در این دستور به صورت پیشفرض بین عناوین و توضیحات اندازه ۳۰ ستون فضای خالی قرار دادم. جهت تغییرش کافیه این عدد رو کم و زیاد کنید تا خروجی مطلوب خودتون به دست بیاد. برای مثال :

\033[36m%-20s\033[0m %s\n
\033[36m%-40s\033[0m %s\n
\033[36m%-15s\033[0m %s\n

تا اینجا برای اینکه کاربر بتونه مستندات رو ببینه باید از دستور make help استفاده کنه. اما میتونیم کار رو ساده تر کنیم با استفاده از DEFAULT_GOAL یک target پیشفرض تعریف کنیم:

.DEFAULT_GOAL := help

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


اگر این کد رو مستقیم کپی می کنید یا اصلا فرقی نداره ٬ اگر از Makefile استفاده می کنید توجه کنید که حتما از Tab برای تعیین فاصله استفاده کنید. جهت سادگی میتونید از Editor Config استفاده کنید.

[Makefile]
indent_style = tab
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
charset = utf-8

جهت اطلاعات بیشتر به این نوشته مراجعه کنید :

سهولت برنامه نویسی تیمی با EditorConfig
یکی از اولین مشکلات کار تیمی هماهنگ کردن Code Style بین برنامه نویس هاست. یکی Tab دوست داره یکی Space. یکی روی ویندوز کار میکنه یکی لینوکس. توسعه یک پروژه و کنترل نسخه در این شرایط خیلی سخت میشه. ولی راه ساده ای برای برطرف کردن این مشکلات وجود داره.