استفاده از clang برای corss compile

اینم یه تجربه پراکنده دیگه!

خب بعد از مدتها دارم مینویسم و از این که این فرصت پیش اومده خوشحالم. موضوعی که الان میخوام در موردش بنویسم اینه که چطور میشه با استفاده clang که یک کامپالر جدید هست، کدی رو برای یک دستگاه embedded که اتفاقا کامپایلر gcc و کل زندگی خودش رو داره کامپایل کرد.

ممکنه بگید این به نظر غیر ممکن میاد اما مخصوصا توی C میشه یه چنین کاری انجام داد و clang و نسخه‌های جدید gcc قدرت این کار رو دارن که کد رو جوری کامپایل کنن که روی دستگاه‌های قدیمی کار کنه. چیزی که نیاز دارید اینکه اول به مجموعه کامپایلر دسترسی داشته باشید و اون کامپایلر حاوی چیزی به اسم sysroot باشه. کلا منظور از sysroot اینکه کل کتابخانه‌ها و ابزارهای لازم برای ساختن یک سیستم عامل کامل برای اون platform رو داشته باشید. دومین چیزی که نیاز دارید اینه که یه چیزی به نام target رو بدونید. این معمولا یه چیز سه بخشی هست که نشون میده cpu چیه، ساختار فایل اجرایی چیه، سیستم عاملی وجود داره یا نه. مثلا به چند مورد ازشون اینهاست armv7l-linux-gnueabihf یا arm-linux-gnueabihf که هر بخش از این سه گانه میگه چی به چیه.

یکی دیگه از چیزایی که مهم میشه اینه که کجا دنبال کامپایلرهای gcc بگرده که خود رو با اونها تطبیق بده خب اون رو هم باید بگید.

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

[/crayon]

حالا این تست رو که بتونید روی بردتون اجرا کنید میتونید اون رو با makefile یا CMake هم ترکیب کنید و چیزهای جالبی بدست بیارد

همین!

لذت برنامه نویسی: ذخیره سازی داده

اینم یه تجربه پراکنده دیگه!

مدتی هست که دوست دارم بنویسم و نمیرسم. حالا بعد از مدتها اشتیاق به نوشتن اونقدر زیاد هست که دارم مینویسم.

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

  1. سایز دیتا و فایل متغیر باشه و بشه اون رو اضافه کرد
  2. فیلدهای دیتا قابل اضافه و کم کردن باشه
  3. حجم کد لازم برای این کار کم باشه که بشه روی دستگاه‌های سطح پایین مختلف ازش استفاده کرد.

راه حل‌ها

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

  1. پیاده‌سازی از اول: یعنی وابسته به نیازمون یه ساختار قابل افزایش خوب پیاده‌سازی کنیم
  2. استفاده از فرمت‌های متنی: فرمت‌های متنی که معمولا میشه استفاده کرد ini, xml, cvs, json, yaml هست. ایراد اینها اینه که چون متن هستن حجم زیادی میگیرن و عملا به صرفه نیستن. معمولا میزان پردازش لازم برای parse اونها هم کم نیست.
  3. استفاده از تکنولوژی های serialization مثل protocol buffer, thrift هست که با استفاده از اونها میتونید یه دیتا رو توصیف کنید و همون دیتا رو بصورت آرایه ذخیره کنید. اونها معمولا از schema evolution یعنی تغییر ساختار دیتا هم پشتیبانی می‌کنن.
  4. استفاده از دیتابیس های سبک مانند sqlite
  5. استفاده از کتابخانه‌های ذخیره سازی nosql مثل unqlite یا level db یا berkeley db که هر کدومشون به ما امکان ذخیره سازی دیتای با فرمت نامشخص رو میدن.

انتخاب من

من در پروژه‌هام از یه ساختار با سایز مشخص، ini ، sqlite و unqlite استفاده کردم. الان ترجیحم استفاده از با این اولویت هست:

  1. اول sqlite چون خیلی امکانانت سطح بالایی میده
  2. استفاده از unqlite و یا ini. البته با ini ما به مشکلات زیادی خوردیم اما unqlite رو هم اونقدر توی فیلد تست نکردم
  3. تهش پیاده کردن ساختار با سایز مشخص هست که کلی دردسر داره

همین

لذت برنامه نویسی: معرفی gerrit

اینم یه تجربه پراکنده دیگه!

خب الان که من فرصت نوشتن بیشتر دارم تصمیم گرفتم یکم در مورد gerrit بنویسم. توی هر شرکتی معمولا یه چیزی به اسم «چرخه حیات توسعه نرم افزار» وجود داره که آدم‌ها ازش بصورت آگاهانه یا به وسیله اونچیزی که فرهنگ اون تیم یا گروه دیکته میکنه ازش استفاده می‌کنند. که اگه عمری بود بیشتر در موردش می‌نویسم. چرخه حیات کارهایی که ما در شرکت انجام می‌دیم به این شکله که:

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

معرفی gerrit

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

روند هم اینه که با امکاناتی که gerrit در اختیار ما قرار میده، بعد از ارسال کد یک مرج محلی اتفاق میفته و کد توسط ci که همون jenkins باشه کنترل و کامپایل میشه. اگه این مرحله درست بود jenkins به gerrit میگه که اگه کسی review کرده میتونه تغییرات رو merge کنه و گرنه تا عدم موفقیت jenkins کسی نمی‌تونه کد رو اشتباهی merge کنه. این تضمین میکنه که دیگه کسی چیز اساسی رو خراب نمی‌کنه. پس از تایید jenkins هم افراد میتونن کد رو بررسی و تغییرات رو merge کنن. همچنین gerrit پلاگین‌هایی داره که کمک میکنه تا ما اون رو به جاهای مختلف مثل jira وصل کنیم و از اینکه یه کار آماده‌است و یا یه کار merge شده با خبر بشیم و حتی وضعیت موارد رو تغییر بدیم.

بعد از بررسی روند کا نوبت به پیش فرضهای هست که توی gerrit وجود داره.

  1. اینکه مدیریت منبع git در کنترل gerrit هست و ازش به عنوان یک git server استفاده می‌شه. یعنی اگه سرور دیگه‌ای مثل github هم وجود داره کپی gerrit هست نه بالعکس
  2. اینکه تغییرات هرچقدر هم که زیاد و مداوم باشن در نهایت در قالب یک commit ارسال میشن. ممکنه بگید که خب اگه من خواستم وسط کارم push کنم چی؟ جواب اینه که هر کامیت یک تغییر هست و تغییر میتونه draft باشه که یعنی هنوز کامل نشده
  3. اینکه gerrit تلاش میکنه که تاریخچه کد خطی بمونه و این در مجموع خوبه. برای خطی نگه داشتن تاریخچه به شدت از rebase استفاده می‌کنه

حالا اگه بخوام بصورت خلاصه مزایا و معایب رو بگم هم اینا به ذهنم میرسه

مزایا و معایب gerrit

مزایا:

  1. تاریخچه خطی
  2. ثبات در عملکرد
  3. وجود ابزار git review برای ارسال به gerrit و راحت کردن استفاده از gerrit
  4. شفافیت روند کاری بررسی کرد
  5. راحتی integrate شدن با ابزارهای دیگه مثل jira و jenkins و gitlab

معایب:

  1. مستندات گنگ. خیلی پیدا کردن چیزهای ساده توی gerrit آسون نیست
  2. زمان زیادی برای یادگیری استفاده از gerrit مورد نیاز هست

در آخر بگم که بگم که روند کاری که هریک از این ابزارها پیشنهاد میدن یک روند دیکته شده است و ممکن به مذاق شما خوش نیاد پس از هر روشی که به مذاقتون خوش میاد استفاده کنید. ما هم یه مدت از gerrit استفاده کردیم بعدش از gitlab و دوباره برگشتیم gerrit چون به نظرمون gerrit بهتر بود

همین!

لذت برنامه نویسی: نحوه کار ما با jenkins

اینم یه تجربه پراکنده دیگه!

خب حدودا سه سال قبل من از جای خالی continues integration نوشتم. حالا حدود بعد از سه سال میخوام دوباره به موضوع رجوع کنم و درباره jenkins بنویسم

توی این سه سال اتفاقات زیادی افتاده. ما روند build رو با استفاده از cmake بهتر کردیم. یادگرفتیم که چی به چیه. حدودا انتهای سال قبل بود که توی شرکت منابع و علم کافی برای پیاده‌سازی روند ci بوجود اومد و ما شروع کردیم به استفاده از jenkins.

نصب و راه‌اندازی jenkins به نسبت آسون و راحت بود. کاری که jenkins برای ما میکرد این بود که به یه دلیلی(بخونید ماشه) شروع به کامپایل کد میکرد. روند کامپایل هم آسون بود. یعنی اینکه یه سری پروژه معمول رو میشناسه مثل cmake, make, gradle, maven داره که کار رو راحت میکنه

ما اول به یه پروژه معمولی که بصورت ساعتی اجرا میشد شروع کردیم فقط نکتش اینه که ما با سخت‌افزارهای مختلف کار میکنیم و این تنها برای یکی از سخت‌افزارهامون کار میکرد. بعدش یه پروژه معمولی دیگه اضافه کردم که با cppcheck کار static analysis رو روی کدمون بصورت ساعتی انجام میداد.

در مرحله بعد با استفاده امکانی از jenkins pipeline تمامی سخت‌افزارها رو باهم کامپایل کردیم و cppcheck هم به عنوان یک مرحله از این خط لوله ما انتخاب شد.

در قدم بعدی هم این ماجرا رو به git وصل کردیم و به ازای هر کامیت یا هر درخواست بررسی که روی کدمون انجام میدیم شروع به کامپایل میکنه و ما میدونیم که دیگه تغییرمون چیزی رو خراب نمیکنه.

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

فعلا همین!

لذت برنامه‌نویسی: ساختن header فایل در cmake

اینم یه تجربه پراکنده دیگه!

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

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

این روند فعال و غیر فعال سازی همیشه با مشکلات جدی همراه بود بدین صورت که همکارها به علت زیاد بودن و متغیر بودن ویژگی‌ها برای مشتری‌ها بایستی این فایل رو زیاد تغییر میدادن و این تغییرات زیاد باعث می‌شد که خطای انسانی داشته باشن و یه چیزایی یادشون بره و یا اضافه کامپایل کنند و به مشتری بدن

کاری که ما کردیم این بود که این هدر خاص که همه این تعاریف توش بود رو بجای دستی ساختن به کمک cmake ساختیم به این شکل که

  1. یه متغیر از نوع cached تعریف کردیم که بشه به عنوان پارامتر زمان کامپایل اضافه بشه
  2. بر اساس اون متغیر برای هریک از ویژگی‌ها یک مقدار فعال و غیر فعال تخصیص دادیم
  3. از اون متغیرها استفاده کردیم و فایل اصلی رو ساختیم

نمونه این کار به این شکل هست که توی CMakeLists.txt یه چنین چیزی نوشتیم

توی فایل

هم یه چنین چیزی هست

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

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

همین!

لذت برنامه نویسی: نوشته‌های من در مورد git

اینم یه تجربه پراکنده دیگه!

من مدتی میشه که با مجله هایو همکاری دوستانه دارم و سعی میکنم مطالبی که عمومی تره رو اونجا بنویسم. حالا توی این مدت من ۳ تا مقاله در مورد git نوشتم که اونجا منتشر شده. پیشنهاد میکنم اگه دوست داشتید برید و بهش سر بزنید

لیست مقالات از این قراره

همین!

پ.ن. خوشحال میشم به مابقی نوشته‌های من توی اونجا سر بزنید.

لذت برنامه نویسی: معرفی ninja سیستم build سریع

اینم یه تجربه پراکنده دیگه!

خب توی این پست یه ابزار جایگزین برای build کردن نرم‌افزار‌های c و c++ معرفی کنم به اسم ninja. داستان بوجود اومدنش از این قراره که آقای اوان مارتین که توی گوگل روی توسعه google chrome کار میکرده متوجه میشه روند کامپایل کدهاشون بیش از حد کنده و دست و آستینش رو بالا زدن که یه سیستم سریعتر برای build بنویسه که حاصل شد ninja. پیشنهاد میکنم در اولین گام سیستمتون رو اگه cmake نیست cmake کنید و در گام دوم از ninja استفاده کنید

ویژگی‌های ninja

خب ویژگی‌هایی که من بلدم از این قراره

  • سعی در انجام یک کار بصورت درست
  • سعی در خیلی سریع بودن
  • حداقل محدودیت روی نحوه کامپیال شدن کد و سپردن اونها به ابزارهای سطح بالاتر مانند cmake و gyp
  • تشخیص درست وابستگی‌ها
  • سرعت بر سادگی ارجحیت دارد

نمونه موردی

خب همونطور که قبلا هم گفته بودم نحوه کامپایل کدها منتقل شده بود به cmake و تنها کاری که لازم بود من انجام بدم دستورات زیر بود

برای کدهای ما در یک تست غیر علمی نتیجه‌های حدودی زیر برای ۲ یا ۳ بار اجرا بدست اومد

type make ninja
clean build ۶۰s ۱۳s
rebuild ۱۰s ۰٫۳s

همین!

غواصی در اعماق: متغیر محلی، عمومی و static در سی و نقش آنها در سرعت کامپایل

اینم یه تجربه پراکنده دیگه!

نوشتن این روزها سخت شده ولی من باز سعی میکنم که یه حضور کمرنگی در صحنه داشته باشم. این دفعه میخواستم در مورد یه موضوع جالب مرتبط با متغیر در سی بنویسیم

مشکل با متغیر محلی بزرگ

ما یه کد سی داشتیم که یه متغیر local با حجم حدودا ۱۰ کیلو بایت تعریف شده بود و مقدار دهی اولیه شده بود(عکس بود). همیشه کامپایلر به این قسمت از کد که میرسید کلی طول میکشید تا بتونه کد رو کامپایل کنه و همیشه اعصاب ما رو خورد میکرد. تا اینکه یه روز تصمیم گرفتیم مشکل رو حل کنیم. راه حل‌هایی که پیش رومون بود اینها:

  1. متغیر رو کلا حذف کنیم. که خب نمیخواستیم صورت مساله رو حذف کنیم
  2. متغیر رو global میکردیم که خب همه میتونستن تغییرش بدن و برامون بد بود
  3. متغیر رو static کنیم. این تفاوتی برای ما نداشت فقط میخواستیم امتحان کنیم.

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

بررسی چرایی راه‌حل

سوالی که برای ما پیش اومد این بود که چرا یه تغییر اینجوری اینهمه هم توی سرعت هم حجم باینری موثره؟ برای جواب دادن به این ماجرا یکم رفتم تحقیق کردم:

  • همه باینری‌ها از بخش‌های مختلفی تشکلی شدن که اگه assembly یادتون باشه شامل اینهاست
  • متوجه شدم که متغیرهای global توی data segment تعریف میشن و متغیرهای local توی stack segment تعریف میشن.
  • متوجه شدم که متغیرهای static هم توی data segment تعریف میشن.

پس هرچی بود زیر سر data segment بود از اینجا به بعدش بازی شد حدس و گمان در مورد چرایی دوتا اتفاق:
– چرا سرعت کامپیال بیشتر؟ احتمالا به این دلیل که کامپایلر با data segment راحت‌تر کار میکنه تا stack segment. اما این حدس حجم باینری کمتر رو توجیه نمیکنه چون در این دوحالت حجم متغیر ثابت مونده
– چرا حجم باینری کمتر؟ برای اینکار یکم باید یادمون بیاد توی assembly که چطور یه متغیر local تعریف میشه منظورم هست. یه متغیر local عملا اینه که اول یه مقداری توی stack قرار میگیره و در یه مرحله دیگه اون مقدار از stack خارج میشه. که این هم نیاز به کد سمت code segment داره هم نیاز به حافظه سمت stack segment داره.

خب با این حالت ما به حدس دقیق تری از اتفاق پشت پرده رسیدیم. اونم اینه که وقتی متغیر توی data segment قرار میگیره یه بخش توسط کامپایلر تخصیص داده میشه و مقادیر هم توی اون خونه‌ها نوشته میشه. ولی وقتی قرار باشه اون متغیر توی stack segment قرار بگیره هم کد تولید میشه هم حافظه تخصیص داده میشه که منجر به طولانی تر شدن کل روند میشه.

امیدوارم به دردتون خورده باشه
همین!

غواصی در اعماق: malloc، پروسه‌ها و حافظه تخصیص یافته

اینم یه تجربه پراکنده دیگه!

چند شب پیش یکی از دوستان با این سوال به سراغ من اومد:

مشکل اینه که وقتی با malloc یا new یک حافظه تخصیص میدی و با free یا delete حافظه رو برمیگیردونی, اون حافظه شاید بلاک هاش خالی بشه اما توی تسک لیست خالی به عنوان منابع استفاده شده میاره و تا پایان پروسه پاک نمیشه. از دوستانی که آشنایی دارن پیگیری کن ببین جریان چیه. مهمه .

خدا خیرت بده. یه جایی از حافظه نمایی رشد میکه و وقتی خالیش میکنیم (با اینکه از لیست heap سیستم عامل خالی میشه) اما توی لیست پروسه‌ها(مثل نتیجه ps) هنوز به عنوان حافظه استفاده شده هست و حافظه آزاد نمیشه !!!‌ عجیبه . کلی توی اینترنت اینو سوال کردن !!

من که لینوکسم. اما فکر کنم ویندوزم همینه. چون سوالای اینترنت هردوشونه

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

پیش‌نیازها

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

  • نکته صفر اینکه با توجه به تحقیقات من زیر کلمه کلیدی new از همون malloc استفاده میشه. پس بررسی malloc به تنهایی میتونه جوابی برای ما فراهم کنه.
  • اول اینکه باید بدونیم که وقتی malloc صدا زده میشه چه اتفاقی میفته. کاری که malloc میکنه اینه که heap پروسه رو بهت اختصاص میده. حالا heap توسط سیستم عامل مدیریت میشه و در صورت نیاز به پروسه‌ی شما حافظه بیشتری تخصیص میده.
  • دوم اینکه فضای heap توسط کرنل مدیریت میشه و به اصطلاح نگاشته میشه به یه چیزی به نام Virtual Memory
  • سوم اینکهVirtual Memory نگاشته میشن به چیزی به نام Memory Page که نزدیک به سخت افزار تره
  • چهارم اینکه Memory Page نگاشته میشن بلوک‌های واقعی حافظه. از این مطمئن نیستم اما این نزدکترین لایه به سخت افزاره که من باهاش آشنا هستم.

بررسی مساله

حالا اگه بخوایم مساله رو بررسی کنیم باید به چند‌تا سوال جواب بدیم:

  • اول اینکه malloc چطور عمل میکنه؟ در پیاده‌سازیش ساز و کارش چیه؟
  • دوم اینکه روند افزایش طول Virtual Memory و اضافه شدن Memory Pageها چطوره؟
  • سوم اینکه بعد از پس دادن Memory Pageها سیستم عامل چطور اونها رو از یک پروسه پس میگیره؟

خب جواب سوالا به ترتیب اینا به نظر میرسن:

  • پیاده‌سازی‌های متفاوتی از malloc وجود داره
    • پیاده‌سازی معمولا اون اینطوریه که پس از گرفتن حافظه وقتی free شد معمولا اونها رو به سیستم عامل پس نمیده و نگه میداره که شاید بعدا بخواد دوباره تخصیص بده.
    • دوم اینکه درخواست یک Memory Page جدید توسط دستوراتی مثل mmap و sbrk درخواست یک بلاک بزرگ حافظه میکنه و اون رو بنا به درخواست نرم‌افزار تخصیص میده.
    • بعدش هم اینکه برای اینکه یک Memory Page رو برگردونه به سیستم عامل بایستی تمام حافظه‌هایی که روی اون تخصیص داده شدن برگشت داده بشه. که این هم همیشه اتفاق نمی‌افته.
    • معمولا هم پیاده‌سازی‌ها اینجورین که حافظه‌ای که توسط sbrk افزایش پیدا کرده رو دست نمیزنن(دلیل دقیقش رو نمیدونم) و حافظه‌ای که توسط mmap اضافه شده رو به راحتی برمیگردونن.
  • تقریبا جواب سوال دوم رو هم تا الان دادم. مقدار Virtual Memory توسط glibc کنترل میشه و بوسیله malloc تخصیص داده میشه.
  • جواب سوال سوم هم اینه که کرنل لینوکس همیشه حافظه برگشت داده شده رو برنمیگردونه چون که هزینه داره فقط اون رو علامت میزنه که در صورت نیاز اون رو حذف کنه یا به swap ببره.

راه حل مساله

حالا بریم سراغ مساله اصلی(چرا حافظه برگشت داده شده برگشت نخورده) و توجیهی که براش پیدا کردیم:
* مهمترین دلیل این اتفاق به نظرم malloc هست که حافظه رو نگه داشته و برنگردونده
* دومین دلیلش هم میتونه این باشه که کرنل هنوز Memory Page رو برنداشته و فقط علامت زده

حالا راه حل چیه:
* اول اینکه به کرنل و glibc اعتماد کنیم
* دوم اینکه بیایم و این پارامتر مروبط به malloc رو تنظیم کنیم۴
* سوم اینکه بیایم از یه پیاده‌سازی دیگه برای تخصیص حافظه و آزاد سازی اون استفاده کنیم
* چهارم اینکه بیایم خودمون مستقم از mmap و munmap استفاده کنیم

امیدوارم اینا راه گشای دیگران هم باشه

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

۱

۲

۳

۴

همین!

لذت برنامه نویسی: ساختار یک پروژه در cmake

اینم یه تجربه پراکنده دیگه!

متاسفانه یه مدت میشه که سرم بیش از اندازه شلوغ شده و نرسیدم وبلاگ بنویسم. قبلا در مورد cmake نشوتم و حالا خواستم یکم بیشتر بنویسم.

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

  1. یه سری سورس که اصل منطق برنامه اونجا پیاده سازی شده
  2. یه فایل اصلی یا main که تابع اصلی نرم افزار اونجا قرار گرفته
  3. احتمالا یه سری تست که نرم افزار رو بصورت اتوماتیک مورد تست قرار میدن
  4. احتمالا یه سری کتابخانه که بصورت static یا dynamic به کد اضافه میشن.

یه خورده توضیح بدم که توی مcmake معمولا یه چیزی داریم به عنوان خروجی که میتونه از جنس فایل اجرایی باشه یا از جنس کتابخانه. ما با ترکیب کتابخانه‌ها و سورس کدهای موجود فایل اجرایی نهایی رو درست میکنیم. روند کار هم معمولا به این صورته که:
۱٫ سورسهای اصلی بصورت یه کتابخانه static کامپایل میشه
۲٫ فایل اصلی برنامه بصورت یک فایل اجرایی کامپایل شده و به کتابخانه سورس‌های اصلی لینک میشه
۳٫ تست کیسها بصوت یک فایل اجرایی کامپایل شده و به کتابخانه سورس‌هاس اصلی لینک میشه.
۴٫ کتابخانه ها هم به فایل اجرایی اضافه میشن.

حالا اگه مثال بخوایم یه پروژه رو مثال بزنم که ساختار کدش به شکل زیره

فایل CMakeLists.txt این پروژه یه چیزی شبیه این خواهد بود

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

همین!