CMake’te Alt Dizinlerle Çalışma

Programları yazarken genellikle bütün işleri tek bir fonksiyonda veya sınıfta yaptırmak oldukça olumsuz sonuçlara neden olur. Örneğin program üzerinde birim testlerinizi yapmak, programlardaki hataları çözmek ve en önemlisi de kodları okumak oldukça zorlaşır. Bu nedenle bir algoritmayı birden fazla küçük fonksiyona bölmek veya bir sınıfı parçalarına ayırıp hiyerarşik olarak birbiri ile ilişkilendirmek programlamanın vazgeçilmez bir parçasıdır. Hatta algoritmalarda bu yönteme Böl ve Fethet (Divide and Conquer) adı verilmektedir.

Programları yazarken sadece fonksiyon veya sınıfları küçük parçalar bölmek genellikle yetersizdir. Örneğin her bir kodu bir adet “main.cpp” dosyasına yazdığınızı düşünün. Bu tip kodları okumak veya test etmek de oldukça güçtür. Hatta projede birden fazla kişi çalışıyorsa, böyle projeler zaman kayıplarına neden olabilir. Bu nedenle kodları çeşitli dosyalara ve onları da çeşitli dizinlere (directory) bölmek oldukça önemlidir. İşte bizim konumuz da tam burada başlıyor. Alt dizinlere (subdirectory) veya modüllere bölünmüş bir projeyi CMake ile nasıl ele alabiliriz? Örneğin şöyle bir proje yapısı hayal edelim:

Figür 1: Örnek Proje Hiyerarşisi

Burada yer alan “CMakeLists.txt” dosyasında, “main.cpp” ve diğer alt dizinler ile alakalı şeyler bulunmak zorunda olacaktır. Ancak bu durum derleme süreci tasarımını oldukça zorlaştırır. Ayrıca alt modülleri bağımsız olarak geliştirebilmek, birden fazla geliştiricinin çalıştığı projelerde oldukça fayda sağlar. Bu nedenle hiyerarşide yer alan her bir bağımsız birimin kendi “CMakeLists.txt” dosyası olması en ideal durumdur. Peki birden fazla parçadan oluşan bu dosyaları ana “CMakeLists.txt” dosyasında nasıl birleştiririz? Çok dizinli projeleri ele almak için CMake’te iki adet komut bulunmaktadır: add_subdirectory ve include.

Bu komutlar aslında basitçe alt dizinlerde yer alan “CMakeLists.txt” dosyalarının içeriklerini çağrıldıkları yere kopyalarlar. Bu da derleme yaparken alt dizinlerin mantıksal olarak ayrı ele alınmasını kolaylaştırır. Ayrıca alt modülleri derleme aşamasında isteğe göre açıp kapatmayı da kolaylaştırır. Ancak bu iki komutun karakteristikleri birbirinden oldukça farklıdır. Öncelikle add_subdirectory komutunun özelliklerinden bahsedip programlama dillerinde de olan Faaliyet Alanı (Scope) kavramının CMake’teki durumunu açıklamaya çalışalım.

add_subdirectory komutu, o projenin herhangi bir alt dizinini derleme sürecine eklemeye yarayan bir komuttur. Bu komuta verilen dizinde bir “CMakeLists.txt” dosyası yer almalıdır. Böylelikle bu komutun çağrıldığı yere alt dizinde yer alan “CMakeLists.txt” dosyasının içeriği kopyalanacak ve oradaki işlemler gerçekleştirilecektir. add_subdirectory komutunun genel yapısı şöyledir:

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

Burada “source_dir” yerine “CMakeLists.txt” dosyasının yer aldığı dizinin ismi yazılır. Eğer göreceli yol (relative path) bilgisi yazılırsa, bu yol o anda derleme işleminde olan “CMakeLists.txt” dosyasının bulunduğu dizin baz alınarak işlem yapılacaktır. Şimdi sadece SubDir isimli bir alt dizini bulunan basit bir örnek ile başlayalım:

# CMakeLists.txt

project(Test)

add_subdirectory(SubDir)
# SubDir/CMakeLists.txt

set(test1 Mustafa)
set(test2 Yemural)

message("${test1} ${test2}")
ÇIKTI
Mustafa Yemural

Yukarıdaki örnekten farkedebileceğiniz üzere, alttaki içeriği add_subdirectory komutunun çağrıldığı yere tamamen kopyalarsanız, aynı sonucu elde edeceksiniz. Siz yukarıdaki örnekteki projeyi derlemeye çalıştığınızda, Generator’dan üretilen çıktı dosyaları da yukarıdaki hiyerarşide oluşmuş olacaklardır. Yani ana dizinde yer alanlar Build dizininin ana kısmında, SubDir altındakiler de yine SubDir altında bulunacaklardır. Ancak siz alt dizinlerin çıktılarının Build dizininde farklı yerlerde olmasını isterseniz, komutta “[binary_dir]” yazan yere bu dizini veya yolu belirtmeniz gerekecektir. Örneğin ben SubDir ile ilgili dosyaların Build dizininde Test/SubDir1 altında olmasını istersem, şu komutu yazmam gerekir:

add_subdirectory(SubDir Test/SubDir1)

Projeyi farklı hedeflere derlemek ile ilgili yazımda daha önce ALL isimli hedeften bahsetmiştim. Bu hedefle yapılan derleme işlemi projedeki bütün hedeflerin derlenmesini sağlamaktaydı. Ancak örneğin add_executable komutunun sonuna EXLUDE_FROM_ALL anahtar kelimesini yerleştirirseniz, bu hedef ALL içerisinden çıkarılırdı. İşte add_subdirectory komutunun sonuna da EXLUDE_FROM_ALL yazarsanız, o alt dizinde yer alan “CMakeLists.txt” dosyasındaki bütün hedefler ALL hedefinden çıkarılacaktır. Eğer bunu belirtmezseniz, projeyi ALL hedefi ile derlediğinizde o alt dizindeki tüm hedefler de onunla birlikte derlenecektir.

Buraya kadar olan her şeyin oldukça basit olduğunu düşünüyorum. Şimdi de birden fazla alt dizinden ve “CMakeLists.txt” dosyasından meydana gelen bir projede oldukça kullanışlı olan bazı CMake değişkenlerinden bahsedelim. Bu değişkenler aslında kullanıldıkları dosyalara göre farklı çıktı verebilirler. Bu nedenle kullanırken oldukça dikkatli olmalısınız. Ben burada öncelikle 4 temel değişkeni açıklayacak ve daha sonra örnekler üzerinden gideceğim:

  • CMAKE_SOURCE_DIR: Kaynak dizinlerinin en üstünde yer alan “CMakeLists.txt” dosyasının dizin bilgisini içerir. Bu değişkenin değeri yazıldığı yere göre değişmez.
  • CMAKE_BINARY_DIR: Build dizinlerinin en üstünün dizin bilgisini içerir. Bu değişkenin değeri yazıldığı yere göre değişmez.
  • CMAKE_CURRENT_SOURCE_DIR: O anda işlemde olan “CMakeLists.txt” dosyasının dizin bilgisini içerir. Bu değişken her add_subdirectory komutu çağrıldığında güncellenir ve bu komut ile iş tamamlandığında tekrar eski haline çevrilir.
  • CMAKE_CURRENT_BINARY_DIR: O anda işlemde olan “CMakeLists.txt” dosyasının Build dizini bilgisini içerir. Bu değişken her add_subdirectory komutu çağrıldığında güncellenir ve bu komut ile iş tamamlandığında tekrar eski haline çevrilir.

Şimdi yine az önceki örnekteki gibi bir hiyerarşi düşünelim. Kaynak dizinde bir adet “CMakeLists.txt” dosyası olsun. Bu dizinin altında SubDir1 ve SubDir2 isimli iki adet alt dizin bulunsun ve her bir alt dizinin de kendilerine ait “CMakeLists.txt” dosyası olsun. Kaynak dizinin bilgisayarda C:/Example altında, Build dizinin ise bilgisayarda C:/Example/Build altında olduğunu düşünelim ve bu Build dizininden cmake .. komutunu çalıştırdığımızı varsayalım. Şimdi dosyaların içeriklerine göre çıkan çıktıyı inceleyelim:

# CMakeLists.txt

project(Test)

message("Main: CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("Main: CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("Main: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("Main: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")

add_subdirectory(SubDir1)

message("Main (After SubDir1): CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("Main (After SubDir1): CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("Main (After SubDir1): CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("Main (After SubDir1): CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")

add_subdirectory(SubDir2 Test)

message("Main (After SubDir2): CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("Main (After SubDir2): CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("Main (After SubDir2): CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("Main (After SubDir2): CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
# SubDir1/CMakeLists.txt

message("SubDir1: CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("SubDir1: CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("SubDir1: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("SubDir1: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
# SubDir2/CMakeLists.txt

message("SubDir2: CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("SubDir2: CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("SubDir2: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("SubDir2: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
ÇIKTI
Main: CMAKE_SOURCE_DIR = C:/Example
Main: CMAKE_BINARY_DIR = C:/Example/Build
Main: CMAKE_CURRENT_SOURCE_DIR = C:/Example
Main: CMAKE_CURRENT_BINARY_DIR = C:/Example/Build
SubDir1: CMAKE_SOURCE_DIR = C:/Example
SubDir1: CMAKE_BINARY_DIR = C:/Example/Build
SubDir1: CMAKE_CURRENT_SOURCE_DIR = C:/Example/SubDir1
SubDir1: CMAKE_CURRENT_BINARY_DIR = C:/Example/Build/SubDir1
Main (After SubDir1): CMAKE_SOURCE_DIR = C:/Example
Main (After SubDir1): CMAKE_BINARY_DIR = C:/Example/Build
Main (After SubDir1): CMAKE_CURRENT_SOURCE_DIR = C:/Example
Main (After SubDir1): CMAKE_CURRENT_BINARY_DIR = C:/Example/Build
SubDir2: CMAKE_SOURCE_DIR = C:/Example
SubDir2: CMAKE_BINARY_DIR = C:/Example/Build
SubDir2: CMAKE_CURRENT_SOURCE_DIR = C:/Example/SubDir2
SubDir2: CMAKE_CURRENT_BINARY_DIR = C:/Example/Build/Test
Main (After SubDir2): CMAKE_SOURCE_DIR = C:/Example
Main (After SubDir2): CMAKE_BINARY_DIR = C:/Example/Build
Main (After SubDir2): CMAKE_CURRENT_SOURCE_DIR = C:/Example
Main (After SubDir2): CMAKE_CURRENT_BINARY_DIR = C:/Example/Build

Gördüğünüz gibi CMAKE_SOURCE_DIR ve CMAKE_BINARY_DIR değişkenlerinin değerleri hangi dosyada olursa olsun değişmemektedir. Burada esas dikkat etmemiz gereken şey diğer iki değişkendir. Ayrıca ikinci alt dizinin Build çıktısını aynı isimli bir dizine değil, Test isimli bir dizine aktarmak istediğimize de dikkat edin. CMAKE_CURRENT_SOURCE_DIR ve CMAKE_CURRENT_BINARY_DIR değişkenleri o anda çağrıldıkları “CMakeLists.txt” dosyaları için farklı çıktı verseler de add_subdirectory komutu ile işimiz bittiğinde tekrar bulundukları “CMakeLists.txt” dosyasına ait çıktıları vereceğini yine örnekten görebiliriz.

Artık temel olarak alt dizinlerle nasıl baş edebileceğimizi öğrendiğimize göre şimdi daha ilginç konulara geçebiliriz. Üstteki “CMakeLists.txt” dosyasında tanımladığımız bir değişken, alttakine görünür mü veya bunun tam tersi mümkün müdür? Peki alt dizinde üst dizine ait bir değişkeni değiştirmek mümkün müdür? Bu gibi sorulara basitçe cevap vermek pek doğru değildir. Programlama dillerinde olduğu gibi CMake’te de Faaliyet Alanı (Scope) adı verilen bir kavram bulunur ve belli başlı faaliyet alanı kuralları mevcuttur.

Bu kurallara geçmeden önce şunu bilmenizde fayda var: CMake bir add_subdirectory çağrısı yaptığında o anda işlediği “CMakeLists.txt” dosyasına ait yeni bir alt faaliyet alanı (child scope) oluşturur. Şimdi bu alt faaliyet alanının bazı özelliklerini inceleyelim:

  • Üst faaliyet alanında tanımlanan tüm değişkenlere, alt faaliyet alanından da ulaşılabilir. Alt faaliyet alanında bu değişkenler tıpkı diğer değişkenler gibi okunabilirler.
  • Alt faaliyet alanında oluşturulan herhangi bir değişken, üst faaliyet alanı tarafından görülemez.
  • Üst faaliyet alanından gelen değişkenlere alt faaliyet alanında yapılan tüm değişiklikler, sadece o anki faaliyet alanını etkilerler ve bu değişiklikler üst faaliyet alanına yansıtılmazlar. Alt faaliyet alanından ayrılırken bu değişkenler üzerinde yapılan değişiklikler kaybolur.

Şimdi şu örneği inceleyelim:

# CMakeLists.txt

project(Test)

set(parentVal test)

message("[PARENT] parentVal (before add_subdirectory) = ${parentVal}")

add_subdirectory(SubDir1)

message("[PARENT] parentVal (after add_subdirectory) = ${parentVal}")
message("[PARENT] childVal (after add_subdirectory) = ${childVar}")
# SubDir1/CMakeLists.txt

message("[CHILD] parentVal (before set) = ${parentVal}")

set(parentVal new_test)
set(childVar test2)

message("[CHILD] parentVal (after set) = ${parentVal}")
message("[CHILD] childVal (after set) = ${childVar}")
ÇIKTI
[PARENT] parentVal (before add_subdirectory) = test
[CHILD] parentVal (before set) = test
[CHILD] parentVal (after set) = new_test
[CHILD] childVal (after set) = test2
[PARENT] parentVal (after add_subdirectory) = test
[PARENT] childVal (after add_subdirectory) =

Gördüğünüz gibi burada 3 kuralın da etkilerini görebiliriz. parentVal değişkeni üst faaliyet alanında tanımlı bir değişken olmasına rağmen alt faaliyet alanında da okunabiliyor, ancak değiştirilemiyor. Ayrıca alt faaliyet alanında tanımlanan childVar değişkenini, add_subdirectory çağrısından sonra bile üst faaliyet alanında göremiyoruz. Elbette illaki üst faaliyet alanındaki bir değişkeni alt faaliyet alanında değiştirmek isterseniz bunun da bir yolu mevcuttur. Bunun için öncelikle set komutunun tam formunu bilmeniz gerekir:

set(<variable> <value>… [PARENT_SCOPE])

Bir değişkenin değerini set ile değiştirirken eğer PARENT_SCOPE anahtar kelimesini kullanırsanız, bir üst faaliyet alanındaki değişkenin değeri değişecektir ve add_subdirectory komutundan sonra da bu değer kullanılacaktır. Ancak burada genellikle bir yanlış anlaşılma olmaktadır. PARENT_SCOPE ile değerini güncellediğiniz değişken üst faaliyet alanındaki değişken olduğundan, bu durum alt faaliyet alanında kopyalanmış olan değişkenin değerini değiştirmeyecektir. Şu örneği inceleyelim:

# CMakeLists.txt

project(Test)

set(parentVal test)

message("[PARENT] parentVal (before add_subdirectory) = ${parentVal}")

add_subdirectory(SubDir1)

message("[PARENT] parentVal (after add_subdirectory) = ${parentVal}")
# SubDir1/CMakeLists.txt

message("[CHILD] parentVal (before set) = ${parentVal}")

set(parentVal new_test PARENT_SCOPE)

message("[CHILD] parentVal (after set) = ${parentVal}")
ÇIKTI
[PARENT] parentVal (before add_subdirectory) = test
[CHILD] parentVal (before set) = test
[CHILD] parentVal (after set) = test
[PARENT] parentVal (after add_subdirectory) = new_test

İlk bakışta sonuç size garip gelebilir. Ancak PARENT_SCOPE anahtar kelimesinin asıl anlamı üst faaliyet alanında yer alan değişkenin değerini değiştirmektir, her iki faaliyet alanında da değişiklik yapmak değildir. Yani bu yöntemle o anki faaliyet alanında değişkenin değerinde herhangi bir değişiklik yapamazsınız. Bu şekilde kullanımlar bazı senaryolarda işinizi görse de genellikle ortalığı biraz karıştırdığından çok tercih edilmemektedir. Zaten add_subdirectory komutunun en büyük avantajı da yeni bir alt faaliyet alanı oluşturup orada izole bir şekilde işlem yapmaya izin vermesidir.

CMake’te diğer dizinlerden bilgi almanın bir yolu da include komutunu kullanmaktır. Bu komut ile bir dosyadaki CMake komutlarını o anki derlemeye dahil edebilirsiniz. Bu komut add_subdirectory gibi bir dizin ismi değil, dosya ismi alır. Komutun iki adet formu bulunmaktadır diyebiliriz:

include(<file> [OPTIONAL] [RESULT_VARIABLE <var>] [NO_POLICY_SCOPE])

include(<module> [OPTIONAL] [RESULT_VARIABLE <var>] [NO_POLICY_SCOPE])

İkinci formu şimdilik burada anlatmayacağım. İlk formda “<file>” kısmına bir dosya ismi yazılmalıdır. Bu dosya ismi geleneksel olarak “.cmake” uzantılı olsa da başka uzantılarda da olabilir. Bu kısma dizin ismi değil de dosya ismi yazılması aslında add_subdirectory komutundan ilk farkıdır diyebiliriz. Bu komutun add_subdirectory komutundan ikinci farkı ise, kullanıldığında yeni bir faaliyet alanı oluşturmamasıdır. Yani include ile “CMakeLists.txt” dosyasına aktarılan içerik o anki faaliyet alanında işlem görür. Elbette buna bağlı olarak ortaya çıkan bariz bir fark da CMAKE_CURRENT_SOURCE_DIR ve CMAKE_CURRENT_BINARY_DIR değişkenlerinin include ile alınan dosyada değişmemesidir. Şimdi şu örneğe bakalım:

# CMakeLists.txt

project(Test)

include(SubDir1/TestInclude.cmake)

message("AUTHOR = ${AUTHOR}")
message("VERSION = ${VERSION}")
message("CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}, CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
# SubDir1/TestInclude.cmake

set(AUTHOR "Mustafa Yemural")
set(VERSION 9.3.5)

message("AUTHOR = ${AUTHOR}")
message("VERSION = ${VERSION}")
message("CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}, CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
ÇIKTI
AUTHOR = Mustafa Yemural
VERSION = 9.3.5
CMAKE_CURRENT_SOURCE_DIR = C:/CMake-Example, CMAKE_CURRENT_BINARY_DIR = C:/CMake-Example/build
AUTHOR = Mustafa Yemural
VERSION = 9.3.5
CMAKE_CURRENT_SOURCE_DIR = C:/CMake-Example, CMAKE_CURRENT_BINARY_DIR = C:/CMake-Example/build

TestInclude.cmake” dosyası SubDir1 dizini içerisinde yer almasına rağmen CMAKE_CURRENT_SOURCE_DIR ve CMAKE_CURRENT_BINARY_DIR değişkenlerinin değerlerinin aynı olduğuna dikkat edin. Ayrıca include ile dahil ettiğiniz dosyada tanımlanan değişkenleri ana “CMakeLists.txt” dosyasında rahatlıkla kullanabiliyoruz. Bu da include ile yeni bir faaliyet alanı oluşturulmadığının en büyük kanıtıdır. Olmayan bir dosyayı include komutu ile eklemeye çalışırsanız CMake size bir hata verecektir:

include(SubDir1/TestInclude2.cmake)
ÇIKTI
CMake Error at CMakeLists.txt:5 (include):
include could not find requested file:

SubDir1/TestInclude2

Bu da doğal olarak konfigürasyon aşamasının tamamlanmasını engelleyecektir. Böyle bir durumdan kaçınmak için OPTIONAL anahtar kelimesini kullanabilirsiniz:

include(SubDir1/TestInclude2.cmake OPTIONAL)

Böylelikle dosya bulunamasa bile CMake hata vermeden çalışmaya devam edecektir. Eğer ki dahil edilen dosyanın tam yol bilgisini elde etmek istiyorsanız RESULT_VARIABLE anahtar kelimesini kullanabilirseniz. Eğer dahil etmek istediğiniz dosya mevcut değilse, bu anahtar kelime ile bildirdiğiniz değişkene NOTFOUND değeri atanacaktır. Örnek:

# CMakeLists.txt

project(Test)

include(SubDir1/TestInclude.cmake RESULT_VARIABLE incRes)
message("Include Result = ${incRes}")
include(SubDir1/TestInclude2.cmake OPTIONAL RESULT_VARIABLE incRes)
message("Include Result = ${incRes}")
ÇIKTI
Include Result = C:/CMake-Example/SubDir1/TestInclude.cmake
Include Result = NOTFOUND

Şu ana kadar bahsettiğimiz tüm faaliyet alanı örnekleri aslında değişkenlerle alakalıydı. CMake’te aslında politikalar (policies) ile ilgili de faaliyet alanı durumu vardır. include komutundaki NO_POLICY_SCOPE anahtar kelimesi de aslında bununla ilgilidir. Politikaları daha sonra anlatacağım için şimdilik bu konuyu geçiyorum.

Peki include ile dahil edilen dosyada yeni bir faaliyet alanı oluşmadığından CMAKE_CURRENT_SOURCE_DIR gibi değişkenlerin o dosyada kullanılmasının pek bir anlamı olmadığını öğrendik. Ancak biz o dosyada çalışırken o dosyayla ilgili yol ve isim gibi bilgileri almak istediğimizde ne yapacağız? Bunun için CMake bize include edilecek olan dosyalarda, dosyanın kendisiyle ilgili bilgileri alabileceğimiz 3 güzel değişken sunar:

  • CMAKE_CURRENT_LIST_DIR: O anda üzerinde işlem yapılan dosyanın bulunduğu dizinin tam yol bilgisini içerir. Burada dosyanın include veya add_subdirectory ile işlenip işlenmediğine bakılmaz.
  • CMAKE_CURRENT_LIST_FILE: O anda üzerinde işlem yapılan dosyanın tam yol bilgisini içerir (dosya adıyla beraber).
  • CMAKE_CURRENT_LIST_LINE: O anda üzerinde işlem yapılan dosyanın, o an hangi satırında işlem yapıldığı bilgisini içerir.

Elbette bu 3 değişken sadece include ile alınan dosyalarda değil her türlü dosyada kullanılabilir. Ancak include ile alınan dosyalarda, üzerinde çalışılan dosya ile ilgili bilgileri alabileceğiniz en iyi değişkenlerdir. Şimdi bununla ilgili bir örnek yapalım:

# CMakeLists.txt

project(Test)

add_subdirectory(SubDir1)
message("----------")
include(SubDir2/IncludeTest.cmake)
# Subdir1/CMakeLists.txt

message("CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
message("CMAKE_CURRENT_LIST_DIR = ${CMAKE_CURRENT_LIST_DIR}")
message("CMAKE_CURRENT_LIST_FILE = ${CMAKE_CURRENT_LIST_FILE}")
message("CMAKE_CURRENT_LIST_LINE = ${CMAKE_CURRENT_LIST_LINE}")
# Subdir2/IncludeTest.cmake

message("CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
message("CMAKE_CURRENT_LIST_DIR = ${CMAKE_CURRENT_LIST_DIR}")
message("CMAKE_CURRENT_LIST_FILE = ${CMAKE_CURRENT_LIST_FILE}")
message("CMAKE_CURRENT_LIST_LINE = ${CMAKE_CURRENT_LIST_LINE}")
ÇIKTI
CMAKE_CURRENT_SOURCE_DIR = C:/Example/SubDir1
CMAKE_CURRENT_BINARY_DIR = C:/Example/Build/SubDir1
CMAKE_CURRENT_LIST_DIR = C:/Example/SubDir1
CMAKE_CURRENT_LIST_FILE = C:/Example/SubDir1/CMakeLists.txt
CMAKE_CURRENT_LIST_LINE = 7
———-
CMAKE_CURRENT_SOURCE_DIR = C:/Example
CMAKE_CURRENT_BINARY_DIR = C:/Example/Build
CMAKE_CURRENT_LIST_DIR = C:/Example/SubDir2
CMAKE_CURRENT_LIST_FILE = C:/Example/SubDir2/IncludeTest.cmake
CMAKE_CURRENT_LIST_LINE = 7

Örnekten include ile eklenen dosya için CMAKE_CURRENT_SOURCE_DIR ve CMAKE_CURRENT_BINARY_DIR değişkenlerinin pek de faydalı olmadığını anlayabilirsiniz. Ancak diğer değişkenler ile include ile eklenen dosyanın ismi ve yolu ile ilgili bilgileri edinebiliyoıruz.

Şimdi include ile ilgili son bir durumu daha ele alıp yazımızı sonlandıralım. Bildiğiniz gibi C ve C++ dillerinde bir başlık dosyasının birden fazla kez kaynak dosyaya dahil edilmesini önlemek için Include Guard adı verilen bir yöntem kullanılırdı. Bazı sistemlerde bunu “#pragma” ile de yapabilirsiniz. Aslında bunun gibi bir durum CMake için de geçerlidir. Yani aynı dosyayı birden fazla kez bir derleme dosyasına dahil etmek, bazı istenmeyen sonuçlara neden olabilmektedir.

Peki bunun çözmenin nasıl bir yolu olabilir. Öncelikle şunu bilmenizde fayda var; return isimli komut ile bir dosyada yaptığınız işlemi anında kesintiye uğratıp çağrılan dosyaya geri dönmeniz mümkündür. Bu komut ile ilgili ayrıntılı özellikleri bir sonraki yazıda açıklayacağım. return komutunun bu özelliğini kullanarak aslında dosyanın başına şu kodları yazabilir ve CMake’te bir Include Guard oluşturabiliriz:

if (INCLUDED_INCLUDE_TEST)
    return()
endif()

set(INCLUDED_INCLUDE_TEST true)

Bu şekilde o dosya bir faaliyet alanında yalnızca bir kez projeye dahil edilebilir. Bu CMake’in hemen hemen tüm versiyonlarında geçerli bir korumadır. Ancak CMake 3.10 versiyonu ile bu işi daha rahat yapabileceğimiz bir komut eklenmiştir. include_guard isimli bu komut ile daha sade ve düzenli bir Include Guard eklemeniz mümkündür. Komutun genel formu şöyledir:

include_guard([DIRECTORY|GLOBAL])

Bu komutu bir dosyanın başına ekleyerek, o dosyanın yalnızca bir kez okunmasını garanti altına alabilirsiniz:

# SubDir2/TestInclude.cmake

include_guard()

set(Test test)

Bu komutun alabileceği iki adet anahtar kelime bulunmaktadır. Bu anahtar kelimeler ve anlamlarını kısaca şöyle açıklayabiliriz:

  • DIRECTORY: Include Guard yalnızca o anda bulunulan dizin ve altında geçerli olacaktır. Dosya bu dizinlerin faaliyet alanlarında sadece 1 kez derlemeye dahil edilebilir. Ancak diğer dizinlerde dahil edilmesi yine mümkün olur.
  • GLOBAL: Include Guard’ı tüm derleme sisteminde ortak olarak uygulanmasını sağlar. O anda üzerinde bu komut bulunan dosya derleme genelinde yalnızca bir kez derlemeye dahil edilebilir. Yani Include Guard faaliyet alanından bağımsız hale gelir.

Bu iki anahtar kelimeye pratikte pek ihtiyaç duyulmasa da bilmenizde fayda vardır. Bu yazıda CMake’te add_subdirectory ve include komutlarından ve bu komutlar arasındaki farklılıklardan bahsettim. Ayrıca faaliyet alanı kavramına değinerek, faaliyet alanını çeşitli örneklerle açıklamaya çalıştım. CMake’te işimize yarayacak çeşitli değişkenleri de örneklerle açıklamaya çalıştım. Bir sonraki yazıda, faaliyet alanıyla yakından ilişkisi bulunan fonksiyonlar ve makroları anlatacak ve faaliyet alanı kavramını biraz daha genişleteceğiz.

5 1 vote
Article Rating
Subscribe
Bildir
guest

0 Yorum
Inline Feedbacks
View all comments