Bir önceki yazıda, farklı dizin yapılarındaki projelerin CMake aracı ile nasıl derlenebileceğini anlatmıştım. Bildiğiniz gibi bir projenin CMake ile derlenebilir olduğu (yani bir CMake projesi olduğu), kaynak dizininde yer alan "CMakeLists.txt" dosyasından anlaşılmaktaydı. Ana kaynak dizininde yer alan bu dosya, CMake ile derleme yapılırken ilk bakılan ve direktiflerin alındığı dosyadır. İşte bu yazıda, bu dosyanın çok basit bir halini kendi projeleriniz için nasıl oluşturabileceğinizi anlatacağım.
CMake Dili
CMake projelerinde "CMakeLists.txt" dosyasına veya sonu genellikle ".cmake" ile biten dosyalara CMake girdi dosyası (CMake input file) adı verilmektedir. Bir projede birden fazla CMake girdi dosyası olabilir ve bu dosyalar çeşitli şekillerle birbirleri ile ilişkilendirilebilir. CMake projelerinde bu dosyaları yazdığımız dile ise CMake Script Dili (CMake Scripting Language) veya basitçe CMake Dili (CMake Language) adı verilmektedir. Bu dil diğer Script dilleri gibi yorumlamaya dayalı bir dildir. CMake aracı bu dilde yazılan dosyaları yorumlayarak esas proje ile ilgili dosyaların üretilmesini sağlar.
"CMakeLists.txt" dosyası, uzantısından da anlayabileceğiniz gibi basit bir yazı (text) tabanlı dosyadır. Bu dosya herhangi bir metin düzenleme editörü ile açılıp düzenlenebilir. Ancak CMake dilinin söz dizimini destekleyen editörler kullanmak daha iyi olacaktır. Böylelikle çeşitli renklendirmeler ile CMake dilinde bulunan çeşitli dil özelliklerini ve komutları birbirinden rahatlıkla ayırt edebilirsiniz. Eğer bir IDE kullanmıyorsanız ben bu konuda Visual Studio Code aracını ve onun eklentilerini önerebilirim.
CMake dili, tıpkı C ve C++ dillerinin desteklediği gibi, yorum satırlarını, değişkenleri, String işlemlerini, dizileri (arrays), fonksiyonları, makroları, koşul ve döngü yapılarını, modülleri başka Script’lere dahil etmeyi (importing) destekler. Bununla beraber her versiyonda dil ile ilgili çeşitli söz dizimi değişiklikleri ve eklemeleri yapılabilir. Bu nedenle yine bu yazıda bahsedeceğim CMake versiyonu konusu oldukça önemli bir konudur.
CMake’te Basit Bir Projenin Yazılması ve Komut Kavramı
Şimdi tek bir kaynak koda sahip basit bir CMake projesi için bir "CMakeLists.txt" dosyası oluşturarak işe başlayalım. Bir CMake girdi dosyasının CMake aracında düzgün bir şekilde ele alınabilmesi için mutlaka minimum versiyon bildiriminin yapılması gerekir. Bununla birlikte bizim yapacağımız örnekte proje ismi bildirimi ve çalıştırılabilir dosya hedefi ile ilgili isim ve kaynak kod dosyası isimlerinin bildirimi yer alacaktır.
Örneğimizde sadece "main.cpp" dosyası yer aldığını düşünürsek, aynı zamanda proje isminin ve çalıştırılabilir dosya isminin Uygulama olmasını istersek, şunun gibi bir Script yazmamız gerekir:
cmake_minimum_required(VERSION 3.10)
project(Uygulama)
add_executable(Uygulama main.cpp)
Gerçekten de yukarıdaki kodu bir "CMakeLists.txt" dosyasına yazıp projenizin bulunduğu yere koyduğunuzda, önceki yazıda gösterdiğim derleme işlemlerini gerçekleştirerek bu projeyi derleyebilirsiniz. Yukarıda her bir satırda yer alan project ve add_executable gibi özel isimlere CMake’te Komut (Command) adı verilmektedir. Komutlar CMake’te gömülü (built-in) olarak bulunurlar ve diğer dillerdeki fonksiyonlara benzerler. Ancak komutlar, diğer dillerdeki fonksiyonların aksine geri dönüş değeri üretemezler.
CMake’te komutlar C ve C++ dillerinde olduğu gibi argüman alırlar, ancak geri dönüş değeri üretmezler. Kullanımları tıpkı void geri dönüş türüne sahip fonksiyonlara benzemektedir. Argümanların alımı ise aralara boşluk karakteri (whitespace cahacter) denilen Space, Tab veya yeni satır gibi karakterler konularak yapılır. Örneğin yukarıda yazdığımız add_executable komutunu şu şekilde de yazabilirdik:
add_executable(Uygulama
main.cpp
)
Bunun yanında CMake komut isimleri konusunda büyük/küçük harf uyumluluğuna dikkat etmez (case-insensitive). Bu nedenle aşağıdaki yazımların hepsi aynı şeyi ifade eder:
add_executable(Uygulama main.cpp)
ADD_EXECUTABLE(Uygulama main.cpp)
Add_Executable(Uygulama main.cpp)
Ancak bir "CMakeLists.txt" dosyasının içini açtığınızda genellikle komut isimlerinin tamamını küçük harfle yazılmış olarak ve kelimelerin arasına alt çizgi (_) eklenmiş halde buluruz (Snake Case). Bu durum ayrıca CMake dokümantasyonlarında da böyledir. Bu nedenle CMake ile ilgili yazılarımda da aynı düzeni kullanacağım.
Komutlarla ilgili bu temel bilgiler yeterlidir, yeri geldiğinde diğer bilgileri de vereceğim. Şimdi minimum bir CMake projesinde yer alan bu 3 temel komutu ayrıntılı bir biçimde inceleyelim. Öncelikle CMake versiyonu ile işe başlayalım.
CMake’te Versiyon Bildirimi
CMake sürekli güncellenen bir araçtır. Bu güncellemeler esnasında ona çeşitli yeni komutlar eklenebilir, varolan komutlar güncellenebilir, dış araçlara olan destek artırılabilir veya varolan hatalar düzeltilebilir. CMake’in geliştiricileri her yeni bir sürüm çıkardığında geriye dönük uyumluluğu (backward compatibility) korumak için de çaba gösterirler.
CMake’te bu versiyon değişimi çok sık olduğundan ve o anda geliştiricilerin ortamında kurulu olan CMake sürümü ile uyumlu olmayan özelliklerin saptanabilmesi amacıyla, CMake’in ana girdi dosyasının başında cmake_minimum_required komutu ile o projeyi çalıştırmak için gerekli olan minimum CMake versiyonunun belirtilmesi gerekir.
cmake_minimum_required komutu bir CMake dosyasının en başında yer almalıdır. Böylelikle belli eylemleri yerine getirmeden önce versiyon kontrolü yapılabilir. Elbette bu kontrolü sisteminizde yüklü olan CMake versiyonu ile yapar. Eğer sisteminizde yer alan sürüm, belirtilen sürümden daha eski ise proje derlenmez.
Eğer proje ana dizinindeki "CMakeLists.txt" dosyasının içine cmake_minimum_required komutu yazılmazsa, CMake bu durumu size bir uyarı iletisi ile bildirir. Çünkü CMake’in projenizin ihtiyaç duyduğu minimum versiyonu bilip buna göre ayarlamaları yapması gerekir. Bu komutun genel formu şu şekildedir:
cmake_minimum_required(VERSION <min>[…<max>] [FATAL_ERROR])
CMake dokümantasyonunda da karşılaşabileceğiniz bu gösterim stilinde "[]" parantezleri seçeneksel kısımları belirtirken, "<>" parantezleri ise geliştircinin yazacağı değer bilgisini belirtir. Burada "[…<max>]" kısmını şimdilik atlıyorum. Ondan daha sonra bahsedeceğim. "<min>" kısmına ayarlamak istediğiniz minimum versiyon bilgisi gelmelidir. Elbette buraya yazacağınız versiyon bilgisinin de bir formatı vardır. Bu formatı şu şekilde inceleyebiliriz:
major.minor[.patch[.tweak]]
Gördüğünüz gibi versiyon bilgisi minimum 2, maksimum 4 alan içerebilir ve alanlar "." ile birbirinden ayrılır. Bu alanları şu şekilde bir tablo ile özetleyelim:
| Versiyon Alanı | Açıklama | Zorunluluk |
|---|---|---|
| major | Yapılan değişiklik geriye dönük uyumluluğu bozan büyük bir değişiklik ise bu versiyon numarası artırılır. | Zorunlu |
| minor | Geriye dönük uyumluluğu bozmadan yeni fonksiyonalite eklendiğinde bu versiyon numarası artırılır. | Zorunlu |
| patch | Varolan versiyonda sadece Bug çözümü yapıldıysa bu versiyon numarası artırılır. | Zorunlu Değil |
| tweak | Genellikle çok fazla değişiklik yapmadan alınan yeni derleme çıktısını belirten versiyon numarasıdır. Çok fazla kullanımı yoktur. | Zorunlu Değil |
CMake’te çoğu değişiklik minor versiyon numarası ile geldiğinden (bu durum CMake 3.0’dan itibaren böyledir), patch ve tweak kısımlarını belirtme zorunluluğunuz yoktur. patch kısmı sadece belli bir versiyonda varolan hatanın düzeltilmiş hali kullanılmak istendiğinde yazılır. CMake 3.X ve sonrası versiyonlarda ise tweak kısmı yer almamaktadır.
cmake_minimum_required komutunda gördüğünüz VERSION gibi isimlere (genellikle büyük harfle yazılırlar) CMake’te Anahtar Kelime (Keyword) adı verilir. Bunların isimleri ve anlamları kullanılan CMake komutuna göre değişir. Bu komutta da versiyon bilgisini belirtmek için, versiyon değerinden önce kullanılmaktadır.
Seçeneksel olan FATAL_ERROR anahtar kelimesinin kullanımı ise CMake 2.6 ve üstü olan sürümlerde anlamsızdır. CMake’in eski sürümlerinde sisteminizde daha düşük sürümlü bir CMake yüklü ise sadece uyarı ile bunu kullanıcıya bildiriyordu. Bu anahtar kelime onu hataya dönüştürmek için yazılırdı. Ama yeni sürümlerde kullanımı gereksizdir. Çünkü varsayılan olarak böyle bir durumda CMake hata verecektir.
cmake_minimum_required komutu sadece CMake’in minimum hangi sürümünün gerekli olduğunu söylemekle kalmaz, ayrıca CMake’te Policy (Politika) olarak adlandırılan bir mekanizmanın kullanımını da etkiler. Policy mekanizması CMake’e "X.Y.Z CMake versiyonu gibi davran" diyebileceğiniz bir yapı sunar. cmake_minimum_required komutunu CMake’e bu Policy davranışlarına göre davranması gerektiğini söylemek için de kullanırız. Ancak bu kullanım, bu yazının kapsamı içinde olmadığından sadece bu bilgiyi verip geçeceğim.
CMake sürümünü belirtmek oldukça önemli bir iştir. Geliştiricilerin bunu dikkatlli düşünmesi gerekir. Genellikle 3.14 gibi bir sürüm çoğu proje için yeterli olur. 3.X öncesi bir sürümü kullanmanız kesinlikle tavsiye edilmez. Çünkü bu sürümler birçok CMake özelliğinden yoksundur. Ancak son sürümleri belirtmek de her zaman doğru değildir. Çünkü bu sefer de projeyi kullanan kişilerin sürekli sistemlerlerindeki CMake sürümünü güncellemesi gerekebilir.
Şimdi bu komutun kullanımı ile ilgili birkaç örnek verip bir sonraki konumuza geçelim:
cmake_minimum_required(VERSION 3.2)
cmake_minimum_required(VERSION 3.10.2)
cmake_minimum_required(VERSION 2.4 FATAL_ERROR)
CMake’te Proje Özelliklerinin Belirtilmesi
CMake’e sadece versiyon bilgisi değil, proje ismi ve projenin hangi dilde olduğu bilgisi de sağlandığında, CMake bu sağlanan bilgilere göre konfigürasyon yapabilir. project komutu tam olarak bu iş için bulunmaktadır. Genellikle en üst seviyede yer alan "CMakeLists.txt" dosyasının içine yazılan bu komut, basit kullanımında projenin ismini ve hangi dil veya dillerde yazıldığını CMake’e bildirir. CMake de buna göre ayarlamaları yapar.
project komutunun iki farklı formu bulunmatadır. İlk formu çok basit, ikinci formu ise daha ayrıntılı bilgiler içerir. Biz önce basit olanından başlayalım:
project(<PROJECT-NAME> [<language-name>…])
"<PROJECT-NAME>" kısmına projenizin ismi yazılır. Bu komut daha sonra bu isimle çeşitli CMake değişkenlerinin değerini ayarlar (bu değişkenlerden daha sonraki yazılarda bahsedeceğim). Proje ismi yalnızca harflerden, rakamlardan ve "_" ile "-" karakterlerinden meydana gelebilir. Pratikte baktığımızda ise genellikle sadece harfler ve "_" karakteri kullanılır. Proje ismi çift tınaklar arasına yazılmadığı için, bu isimde boşluklara yer verilmez.
Komutta "[<language-name>…]" kısmına ise, projede kullanılan programlama dillerinin listesi gelir. Elbette bunu yazma zorunluluğumuz yoktur. Ancak belirtmezsek, CMake otomatik olarak o projeyi bir C veya C++ projesi olarak algılayacaktır. Uygulama isimli ve C ile C++ dillerini kullanan bir projeyi şu şekilde belirtebiliriz:
project(Uygulama C CXX)
CMake’te dil kısmına yazılabilecek seçenekler oldukça fazladır. Her yeni versiyonda ise bu seçenekler çoğalmaktadır. Buraya boşluklarla ayırarak birden fazla dil de girilebilir. Böylelikle CMake yine bazı değişkenlerin değerini ayarlayacak ve derleyicileri otomatik olarak buna göre konfigüre edecektir. Bu kısma yazılabilecek dil seçenekleri, her seçeneğin hangi dili belirttiği ve seçeneğin hangi versiyonda CMake’e eklendiğini şu şekilde bir tablo ile verelim:
| Dil Seçeneği | Dil | Eklendiği Versiyon |
|---|---|---|
| C | C | |
| CXX | C++ | |
| Fortran | Fortran | |
| CSharp | C# | CMake 3.8 |
| CUDA | Nvidia CUDA | CMake 3.8 |
| Swift | Swift | CMake 3.15 |
| OBJC | Objective-C | CMake 3.16 |
| OBJCXX | Objective-C++ | CMake 3.16 |
| ISPC | Intel ISPC | CMake 3.18 |
| HIP | HIP | CMake 3.21 |
| ASM | Assembler | |
| ASM_NASM | Netwide Assembler (NASM) | |
| ASM_MASM | Microsoft Macro Assembler (MASM) | |
| ASM-ATT | AT&T Assembler | |
| ASM_MARMASM | Microsoft ARM Assembler (MARMASM) | CMake 3.26 |
Dili belirtmediğiniz sürece CMake’in varsayılan değeri C CXX olarak tutacağını söylemiştim. Ancak gerçek anlamda dil belirtmemek de mümkündür. Dil kısmına NONE yazdığınızda veya birazdan göreceğiniz ikinci kullanımda LANGUAGES kısmını boş olarak verdiğinizde CMake’e herhangi bir dil için ayar yapmamasını söylersiniz. Ancak bu kullanım çok tavsiye edilmemektedir. Şimdi project komutunun ikinci formuna bakalım:
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>…])
Bu form, CMake’in bu yazı yazılırken varolan en son sürümü olan 3.35.1 sürümünden alınmıştır. Siz bu yazıyı okuduğunuz zamanki form farklı olabilir. Burada bunun üzerinden konuşacağım.
İkinci formumuz oldukça fazla detay içerse de genellikle sadece "<PROJECT-NAME>" ve "[LANGUAGES <language-name>…]" kısımlarını kullanırız. Buradaki "[LANGUAGES <language-name>…]" kısmının ilk formda programlama dili belirttiğimiz kısımdan tek farkı, başına LANGUAGES anahtar kelimesinin gelmesidir. Aslında daha çok bu şekilde kullanım önerilmektedir. Hemen bir örnek verelim:
project(MyApp LANGUAGES CXX)
Burada MyApp isimli projede sadece C++ dilinin kullanılacağı belirtilmiştir. project komutunda yer alan "[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]" kısmına proje versiyonu, "[DESCRIPTION <project-description-string>]" kısmına projenin açıklaması, "[HOMEPAGE_URL <url-string>]" kısmına ise projenin web sayfasının URL adresi gelebilir. Elbette bunları kullanma zorunluluğunuz yoktur. Tam formu kullanan bir örnek verelim:
project(MyProject VERSION 1.1
DESCRIPTION "Test Project Description"
HOMEPAGE_URL "http://www.mytestprojecturl.com"
LANGUAGES C CXX)
Bu kullanımların faydası, CMake’in pek çok farklı değişkenin değerini arka planda bu verdiğimiz değerlere göre otomatik olarak ayarlamasıdır. Daha sonra projenin farklı bölgelerinde bu değerlere ihtiyaç duyduğumuzda doğrudan bu değişkenleri kullanabiliriz. Ancak bu değişkenlerin kullanımına burada çok girmeyeceğim için şimdilik bu örneğin yeterli olduğunu düşünüyorum.
Bu konuyla ilgili son olarak CMake’te herhangi bir project komutu bulunmadığında ne olacağına değinelim. Bu komut ile dil belirtmediğimizde zaten C ve C++ dillerinin varsayılan olarak ayarlanacağını söylemiştik. Ancak komutun tamamen olmadığı durumda proje ismi ne olur? CMake dokümanlarında project komutu kullanılmadığında, aracın varsayılan olarak şöyle bir komut oluşturduğu söylenmektedir:
project(Project)
Yani proje ismini varsayılan olarak Project yapacaktır. Ayrıca şuna benzer bir uyarı mesajı verecektir:
CMake Warning (dev) in CMakeLists.txt:
No project() command is present. The top-level CMakeLists.txt file must
contain a literal, direct call to the project() command. Add a line of
code such as
project(ProjectName)
near the top of the file, but after cmake_minimum_required().
CMake is pretending there is a "project(Project)" command on the first
line.
This warning is for project developers. Use -Wno-dev to suppress it.
CMake’e Çalıştırılabilir Dosya Hedefinin Eklenmesi
Şimdi son komutumuz olan add_executable komutuna geçelim. Ancak bu komutu incelemeden önce CMake’te hedef (target) kavramına bir bakış atmamız gerekir. Bir sonraki yazıda bu konuyla ilgili zaten ayrıntılı bir şekilde konuşulacaktır. Şimdilik kısaca bahsedelim.
Hedef (target) dediğimiz kavram CMake’te belki de en önemli kavramdır. Bu kavram CMake’in çıktı nesnesini belirtir. Kendisi bir çalıştırılabilir dosya, bir kütüphane veya çok farklı bir şey olabilir. CMake’te hedef oluşturan komutlar şunlardır:
- add_library
- add_executable
- add_custom_target
Ben burada çalıştırılabilir dosya hedefi oluşturmaya yarayan add_executable komutundan kısaca bahsedeceğim. Bu komut ile CMake’e çeşitli kaynak dosyaları kullanarak bir çalıştırılabilir dosya oluşturmasını söyleriz. Bu komutun normal çalıştırılabilir dosyalar için kullanılan formu şöyledir:
add_executable(<name> <options>… <sources>…)
Bu yazıda sadece bu komutun isim ve kaynak dosyalar kısmı üzerinde duracağım. Verilen seçenekleri bir sonraki yazıda zaten göreceğiz. "<name>" kısmına oluşturulacak olan çalıştırılabilir dosyanın ismi yazılır. Bu isim de harflerden, sayılardan, "_" ve "-" karakterlerinden meydana gelebilir.
Komutun sonunda yer alan "<sources>" kısmına ise bu hedef için derlenecek kaynak dosyaların isimleri yazılır. Kaynak dosya isimleri birbirinden boşluklarla ayrılabilir. Şimdi aşağıdaki örneğe bakalım:
add_executable(MyTestApp main.cpp)
Bu şekilde "main.cpp" kaynak dosyası kullanılarak MyTestApp isimli çalıştırılabilir dosya oluşturulacaktır. Bu çalıştırılabilir dosya Windows sistemlerinde tam ismi "MyTestApp.exe", UNIX benzeri sistemlerin çoğunda ise tam ismi "MyTestApp" olarak oluşacaktır. Bir "CMakeLists.txt" dosyasında ayrıca birden fazla çalıştırılabilir dosya ismi belirtmek mümkündür. Bunun için add_executable komutunu birden fazla kez farklı isimler ile çağırmamız gerekir. Eğer aynı isim ile kullanılırsa, CMake bu durumda bir hata verecektir.
Elbette add_executable komutunun pek çok farklı formu olsa da bunlar bu yazının kapsamı dışındadır. CMake’in diğer dillere benzer pek çok özelliğinin olduğunu söylemiştik. Yorum satırları (comment lines) da bu özelliklerden biridir. Yorum satırlarını kullanmak programlama dillerinde olduğu gibi burada da ciddi önem arz eder.
CMake tıpkı Unix Shell Script’lerinde olduğu gibi "#" karakteri ile yoruma izin verir. Bu karakterden sonrası CMake’te yorum olarak ele alınır. Aşağıdaki kodlarda, ilk başta verdiğimiz minimal proje örneğinin değişik bir versiyonunun yorumlanmış halini görebilirsiniz:
cmake_minimum_required(VERSION 3.14) # Minimum CMake version is 3.14
# Use only C++ Compiler for "Uygulama" project:
project(Uygulama LANGUAGES CXX)
# Add executable for this project:
add_executable(Uygulama1 main.cpp)
# Add another executable:
add_executable(Uygulama2 main.cpp)
Bu yazıda genel olarak basit bir CMake projesninin nasıl oluşturulacağını ve bir CMake girdi dosyasının içeriğini anlatmaya çalıştım. Ayrıca CMake’teki komut ve anahtar kelimeler gibi kavramları ve bazı temel komutları da gösterdim. En son da CMake’te nasıl yorum satırı ekleyebileceğimizi gösterdim. Bütün bu gördüklerimiz 1-2 kaynak kod dosyasından oluşan projelerimizi sadece derleyip çalıştırılabilir dosya haline getirmek istediğimizde oldukça faydalı bilgilerdir. Ancak daha büyük projeler için daha fazla CMake özelliğini bilmemiz gerekir. Bunu da sonraki yazılarda yapacağız.

