آموزش کار با CMake

در این مقاله به آموزش کار با CMake می‌پردازیم. برای کار با CMake نیاز به دانش برنامه‌نویسی زبان C یا برنامه‌نویسی زبان ++C دارید و جز آموزش‌های پیشرفته زبان C و ++C حساب می‌شه.

CMake چیست؟

ابزار cmake یک سیستم قابل توسعه برای مدیریت پروسه build بدون وابستگی به کامپایلر در یک سیستم‌عامل است. فایل‌های پیکربندی ساده‌ای در هر پوشه به اسم  CMakeLists.txt قرار می‌گیرد که برای تولید خروجی‌های build استاندارد از جمله MakeFiles برای Linux استفاده می‌شود. CMake یک زبان اسکریپت نویسی برای build است و Syntax مخصوص خود را دارد.

آموزش کار با CMake

ساختار فایل‌های CMake

فایل‌های CMake بصورت  CMakeLists.txt یا project_name.cmake ذخیره می‌شوند. البته بعنوان روش شایسته و تمیز کدنویسی بهتر است اسکریپت اصلی به شکل  CMakeLists.txt نام گذاری شود.

فایل CmakeLists.txt در کنار فایل‌های سورس پروژه‌ای که می‌خواهید کامپایل کنید قرار می‌گیرد. اگر پروژه‌ دارای چندین ماژول است و هر کدام توانایی کامپایل جداگانه را دارند در زیرپوشه هر ماژول یک فایل جداگانه  CMakeLists.txt می‌تواند قرار گیرد. همچنین فایل‌های cmake. بصورت ماژول هم میتوانید داشته باشید.‌

دستورات make شبیه توابع و متد‌های زبان‌های برنامه نویسی ++C و Java است. در زیر دستورات پرکاربر آن لیست شده است. دستورات در cmake حساس به بزرگی و کوچکی حروف نیست.

  • دستور message: پرینت کردن پیام دلخواه
  • دستور cmake_minimum_required: مشخص کردن حداقل نسخه cmake برای استفاده
  • دستور add_executable: اضافه کردن خروجی اجرایی
  • دستورadd_library: اضافه کردن کتابخانه برای کامپایل شدن همراه سورس
  • دستور add_subdirectory: زیرپوشه‌ای را به سیستم build اضافه می‌کند.

البته دستورات شرطی و کنترلی زیر هم در cmake وجود دارند.

if, endif

elif, endif

while, endwhile

foreach, endforeach

متغیرهای محلی CMake

این متغیرها برای کنترل عملکرد و راهنمایی کامپایلر در هنگام پروسه build استفاده می‌شوند. مثلا با اضافه کردن Wall به متغیرCMAKE_CXX_FLAGS تمامی هشدارهای موقع کامپایل فعال می‌شوند. روش انجام این‌ کار بصورت زیر است.

set(CMAKE_CXX_FLAGS "-Wall")

دستور set برای مقدار دهی متغیرها استفاده می‌شود. برای اینکه مقدار قبلی متغیر از بین نرود و مقدار فعلی به آن اضافه شود می‌توانید از دستور زیر استفاده کنید.

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

متغیرها

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

مطلب پیشنهادی:  وراثت در ++C

دسترسی به متغیرها

با قرار دادن متغیر بین {}$ می‌توانید به مقدار آن دسترسی داشته باشید.

message("CXX Standard: ${CMAKE_CXX_STANDARD}")

مقدار دهی به متغیرها

برای مقدار دهی به متغیرها از دستور set استفاده می‌شود.

set(CMAKE_CXX_STANDARD 14)

لیست‌ها در CMake

تمام مقادیر در cmake بصورت رشته ذخیره‌ می‌شوند ولی میتوانید بعنوان لیست با آن‌ها رفتار کنیم. مقادیر با ; از هم جدا میشوند.

set(files a.txt b.txt c.txt)
# sets files to "a.txt;b.txt;c.txt"

برای دسترسی به مقادیر موجود در لیست از دستور foreach می‌توانید استفاده کنید.

foreach(file ${files})
message("Filename: ${file}")
endforeach()

مثلاً فرض کنید برنامه زیر را می‌خواهید کامپایل کنید.

$ vim main.cpp

یک فایل برنامه به اسم main.cpp با محتوای زیر ایجاد کنید.

#include <iostream>

int main() {

std::cout<<"Hello Wordl! I'am Milad"<<std::endl;

}

بعد با کامپایلر آن را می‌توانید کامپایل و فایل اجرایی بگیرید.

$ g++ main.cpp -o myapp

و آن را اجرا می‌کنید.

$ ./myapp

Hello Wordl! I'am Milad

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

$ vim CmakeLists.txt

یک فایل به اسم CmakeLists.txt با محتوای زیر ایجاد کنید. خط اول حداقل ورژن CMake لازم برای build این برنامه را تعیین می‌کند. و خط دوم اسمی را برای پروژه در نظر میگیرد و خط سوم از سورس کد فایل اجرایی تولید می‌کند.

cmake_minimum_required(VERSION 3.9.1)

project(CMakeTutorialExample)

add_executable(myapp main.cpp)

و الان CMake را اجرا کنید.

$ cmake CmakeLists.txt

و خروجی اجرا شبیه زیر خواهد شد:

$ cmake CMakeLists.txt

-- The C compiler identification is GNU 7.5.0

-- The CXX compiler identification is GNU 7.5.0

-- Check for working C compiler: /usr/bin/cc

-- Check for working C compiler: /usr/bin/cc -- works

-- Detecting C compiler ABI info

-- Detecting C compiler ABI info - done

-- Detecting C compile features

-- Detecting C compile features - done

-- Check for working CXX compiler: /usr/bin/c++

-- Check for working CXX compiler: /usr/bin/c++ -- works

-- Detecting CXX compiler ABI info

-- Detecting CXX compiler ABI info - done

-- Detecting CXX compile features

-- Detecting CXX compile features - done

-- Configuring done

-- Generating done

-- Build files have been written to: /home/milad/Desktop/test

و فایل‌های ایجاد شده شبیه زیر خواهند شد:

$ tree

.

├── CMakeCache.txt

├── CMakeFiles

│   ├── 3.10.2

│   │   ├── CMakeCCompiler.cmake

│   │   ├── CMakeCXXCompiler.cmake

│   │   ├── CMakeDetermineCompilerABI_C.bin

│   │   ├── CMakeDetermineCompilerABI_CXX.bin

│   │   ├── CMakeSystem.cmake

│   │   ├── CompilerIdC

│   │   │   ├── a.out

│   │   │   ├── CMakeCCompilerId.c

│   │   │   └── tmp

│   │   └── CompilerIdCXX

│   │       ├── a.out

│   │       ├── CMakeCXXCompilerId.cpp

│   │       └── tmp

│   ├── cmake.check_cache

│   ├── CMakeDirectoryInformation.cmake

│   ├── CMakeOutput.log

│   ├── CMakeTmp

│   ├── feature_tests.bin

│   ├── feature_tests.c

│   ├── feature_tests.cxx

│   ├── Makefile2

│   ├── Makefile.cmake

│   ├── myapp.dir

│   │   ├── build.make

│   │   ├── cmake_clean.cmake

│   │   ├── DependInfo.cmake

│   │   ├── depend.make

│   │   ├── flags.make

│   │   ├── link.txt

│   │   └── progress.make

│   ├── progress.marks

│   └── TargetDirectories.txt

├── cmake_install.cmake

├── CMakeLists.txt

├── main.cpp

└── Makefile

خب فایل Makefile آماده است الان می‌توانید فایل اجرایی را با کمک Makefile درست کنید.

$ make

Scanning dependencies of target myapp

[ 50%] Building CXX object CMakeFiles/myapp.dir/main.cpp.o

[100%] Linking CXX executable myapp

[100%] Built target myapp

و برنامه اجرایی شما به اسم myapp آماده شد.

مطلب پیشنهادی:  اورلود کردن عملگر دسترسی کلاس (->) در ++C

خب برنامه را کمی پیچیده کنید. یکی از ویژگی‌های ++C نسخه ۱۴ را استفاده کنید و به cmake بگویید  که از این نسخه استفاده کند.

#include <iostream>

auto sum(int a, int b){

return a + b;

}

int main() {

std::cout<<"Hello Wordl! I'am Milad"<<std::endl;

std::cout<<"Sum of 3 + 4 :"<<sum(3, 4)<<std::endl;

return 0;

}

و فایل CMakeLists.txt را بصورت زیر تغییر دهید.

cmake_minimum_required(VERSION 3.9.1)

project(CMakeTutorialExample)

set(CMAKE_CXX_STANDARD 14)

add_executable(myapp main.cpp)

و مجدد cmake را اجرا کنید.

$ cmake CMakeLists.txt

-- Configuring done

-- Generating done

-- Build files have been written to: /home/milad/Desktop/test

و فایل اجرایی را تولید کنید.

$ make

Scanning dependencies of target myapp

[ 50%] Building CXX object CMakeFiles/myapp.dir/main.cpp.o

[100%] Linking CXX executable myapp

[100%] Built target myapp

و فایل myapp را اجرا کنید.

$ ./myapp

Hello Wordl! I'am Milad

Sum of 3 + 4 :7

چندسکویی کردن برنامه

فرض کنید موقع build می‌خواهید چک کنید اگر سیستم‌عامل Windows بود یک خروجی و اگر Unix بود یک خروجی دیگر تولید کند. البته اطلاعات خیلی دقیق‌تری هم Cmake می‌‌تواند تولید کند. ولی برای این مثال نوع سیستم عامل کافی است.

cmake_minimum_required(VERSION 3.9.1)

project(CMakeTutorialExample)

set(CMAKE_CXX_STANDARD 14)

if(UNIX)

message("This is a ${CMAKE_SYSTEM_NAME} system")

elseif(WIN32)

message("This is a Windows System")

endif()

add_executable(myapp main.cpp)

 

و خروجی اجرای cmake بشکل زیر خواهد شد:

 

…

-- Detecting CXX compile features - done

This is a Linux system

-- Configuring done

…

تعریف ماکرو

در ادامه با دانستن نوع سیستم‌عامل و استفاده از ماکروها در صورت نیاز در داخل کد می‌توانید تصمیماتی را بگیرید. با دستور add_definitions و پرچم D- می‌توانید ماکرو تعریف کنید.

cmake_minimum_required(VERSION 3.9.1)

project(CMakeTutorialExample)

set(CMAKE_CXX_STANDARD 14)

if(UNIX)

message("This is a ${CMAKE_SYSTEM_NAME} system")

add_definitions(-DCMAKEMACROSAMPLE="unix")

elseif(WIN32)

message("This is a Windows System")

add_definitions(-DCMAKEMACROSAMPLE="Windows")

endif()

add_executable(myapp main.cpp)

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

#include <iostream>#ifndef CMAKEMACROSAMPLE

#define CMAKEMACROSAMPLE "NO SYSTEM NAME"

#endifauto sum(int a, int b){

return a + b;

}int main() {

std::cout<<"Hello CMake!"<<std::endl;

std::cout<<CMAKEMACROSAMPLE<<std::endl;

std::cout<<"Sum of 3 + 4 :"<<sum(3, 4)<<std::endl;

return 0;

}

معمولاً برای اینکه سورس کد شلوغ نشود cmake را داخل یک پوشه اجرا می‌کنند تا خروجی را در آنجا بنویسد. و البته با متغیرهای محلی هم می‌توانید خروجی‌ها را تعیین کنید. همچنین با متغیرهای محلی آدرس خروجی کتابخانه‌های اشتراکی و ثابت را هم می‌توانید تعیین کنید.

مطلب پیشنهادی:  کامنت گذاری در ++C

و همچنین خروجی باینری را با دستور زیر می‌توانید تغییر بدید.

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)

ساخت کتابخانه با CMake

اضافه کردن کتابخانه‌های استاتیک

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

add_executable(myapp main.cpp lib/testlib.hpp libs/math/testlib.cpp)

اضافه کردن کتابخانه‌های اشتراکی

با دستورadd_library می‌توانید کتابخانه‌های اشتراکی را بسازید.

add_library(math SHARED libs/math/testlib.cpp)

همچنین می‌توانید یک فایل CMakeLists.txt جداگانه در پوشه کتابخانه‌ها بنویسید و آن را به CMakeLists.txt اصلی وصل کنید.

add_subdirectory(lib/math)

add_executable(myapp main.cpp)

target_link_libraries(myapp math)

پیدا کردن کتابخانه‌ها با دستور cmake

فرض کنید دنبال کتابخانه boost نسخه 1.66 هستیم در این صورت با دستورfind_package می‌توانید مسیر آن را پیدا و به فایل اجرایی اضافه کنید.

find_package(Boost 1.66)

if(Boost_FOUND)

message("Boost Found")

include_directories(${Boost_INCLUDE_DIRS})

target_link_libraries(myapp ${Boost_LIBRARIES})

elseif(NOT Boost_FOUND)

error("Boost Not Found")

endif()

وقتی دستور find_package، کتابخانه‌ یا بسته مورد نظر را پیدا کند بصورت خودکار متغیرهای زیر مقدار دهی می‌شوند.

<NAME>_FOUND :
<NAME>_INCLUDE_DIRS or <NAME>_INCLUDES :
<NAME>_LIBRARIES or <NAME>_LIBRARIES or <NAME>_LIBS :
<NAME>_DEFINITIONS

اگر از cmake استفاده نمی‌کردید دستورکامپایل بشکل زیر می‌شد.

g++ main.cpp -o cmake_hello -I/home/milad/libraries/boost/include -L/home/milad/libraries/boost -lBoost

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

کامنت یادتون نره و اگر آموزش کار با CMake براتون مفید بود ما را نیز دعا کنید.

اگر این نوشته‌ برایتان مفید بود لطفا کامنت بنویسید.

دیدگاهتان را بنویسید

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

6 دیدگاه

  1. سلام وقت بخیر
    ممنون از آموزش خوبتون
    من برای اجرای یک مدل اپن سورس از CMake استفاده کردم. اما ارور زیر رو دریافت میکنم:
    Selecting Windows SDK version 10.0.19041.0 to target Windows 10.0.18363.
    The Fortran compiler identification is unknown
    CMake Error at CMakeLists.txt:16 (PROJECT):
    No CMAKE_Fortran_COMPILER could be found.

    gfortran رو هم نصب کردم اما همچنان این ارور وجود داره . ممنون میشم اگر منو راهنمایی بفرمایید.

  2. یکم اش رو خوندم گفتم فراموش میکنم نظر بدم بعد برم بقیش رو بخونم عالیه دستت درد نکنه .
    لطفا بیشتر در موضوعات مربوط به c++ پست بگذارید .
    باتشکر فراوان

  3. عالی بود
    ممنون

  4. سلام و خدا قوت
    بسیار مطلب مفیدی بود، تشکر میکنم بابتش
    منتظر پست های بعدی شما هستیم

  5. بسیار عالی بود