CMake’te Değişkenler

Birçok programlama dilinde değişken (variable) kavramına aşinasınızdır. Elbette değişkenler genelde bellek bölgelerini temsil ederler ve yazdığınız program bellekte varolduğu sürece veya kullandığınız programlama dilinin kuralları çerçevesinde varlıklarını sürdürürler. Diğer programlama dillerindeki değişkenlere benzer olarak CMake de bize değişken tanımlama ve kullanma olanağı verir. Böylelikle "CMakeLists.txt" dosyasında belli bilgileri geçici olarak onlarda depolayabilir ve gerektiğinde de onları kullanabiliriz.

Değişkenleri Tanımlamak

Diğer programlama dillerinde olduğu gibi CMake’te de değişkenleri tanımlamanın ve kullanmanın bir usulü vardır. Bir de yine diğer programlama dillerinde olduğu gibi değişkenleri isimlendirmenin de bir kuralı vardır. Değişken isimlendirme kurallarından birazdan bahsedeceğim, ancak şimdi "CMakeLists.txt" dosyasında basit bir değişkeni nasıl tanımlayabileceğimizi görelim. CMake’te değişkenleri set komutu ile tanımlarız. Normal bir değişkeni tanımlamak için bu komutun genel formu şöyledir:

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

Burada "<variable>" değişkenin ismini, "<value>" ise değişkenin alacağı değeri belirtir. Değişken ismi İngilizce büyük/küçük harflerden, rakamlardan ve "_" ile "–" karakterlerinden meydana gelebilir. Ancak burada dikkat etmeniz gereken konu, değişken isimleri harflerin büyük/küçük olmasına duyarlıdır (case-sensitive). Hatırlarsanız CMake’teki komutların büyük/küçük harf uyumluluğu kuralı yoktu. Değişken isimleri ayrıca ".", "/" ve "+" gibi karakterleri de alabilse de bunlar pratikte oldukça nadir görülür. Bunun dışındaki karakterleri kullanmak da dolaylı olarak mümkün olsa da pratikte hemen hemen hiç biri kullanılmaz.

Komutun sonuna seçeneksel olarak yerleştirebileceğiniz PARENT_SCOPE isimli anahtar kelime ile tanımladığınız değişkenin Scope’u (Faaliyet Alanı) ile oynayabilirsiniz. Ancak bunu bu yazıda anlatmayacağım. Scope ile alakalı konuları sonraki yazılarda anlatmayı düşünüyorum. Son olarak "<value>" kısmını anlatıp örneklerimize geçelim.

CMake aslında bütün değişkenleri birer String (Karaker Katarı, Dizge) olarak ele alır. Yani işin sonunda onlar sadece birer String olurlar. Bir değişkene değer verirken, eğer bu değer boşluklar içermiyorsa, bu değeri çift tırnak (") arasına almamıza gerek yoktur. Eğer değişkene birden fazla değer verilirse bunlar ";" karakteri ile birbirine bağlanırlar. Sonuçta bu gibi String’lere CMake’te Liste (List) adı verilir. Şimdi örnekleri inceleyelim:

set(test_var1 x) # test_var = "x"
set(test_var2 x y z) # test_var2 = "x;y;z"
set(test_var3 "x y z") # test_var3 = "x y z"
set(test_var4 x;y;z) # test_var4 = "x;y;z"
set(test_var5 x "y z") # test_var5 = "x;y z"
set(test_var6 x;y z) # test_var6 = "x;y;z"

Gördüğünüz gibi tırnak içine almadan boşluk bıraktığınızda, otomatik olarak ";" karakteri oraya yerleştirilmektedir ve bu da bir listeyi belirtmektedir. Şimdi "CMakeLists.txt" dosyası içerisinde bu tanımladığımız değişkenlerin değerlerini nasıl elde edebileceğimize bakalım.

Değişkenleri Kullanmak

Tıpkı Bash Script’e benzer olarak, CMake’te bir değişkenin değerini elde etmek için ${varName} biçimi kullanılır. Burada varName yerine tanımladığımız değişkenin adı yazılır. Bu yazım biçimi String veya değişkenin değerinin beklendiği herhangi bir yerde kullanılabilir. Şimdi bununla ilgili birkaç örnek verelim:

set(test xyz) # test = "xyz"
set(test2 ${test}w) # test2 = "xyzw"
set(test3 ${test} abc) # test3 = "xyz;abc"

CMake değişkenleri oldukça esnek bir yapıya sahiptir. Bir değişkenin ismini yazarken başka bir değişkenin değerini kullanmaya ve değişken değerlerini özyinelemeli (recursive) olarak kullanmaya izin verir. Bunun yanında CMake bir değişkeni kullanmak için onun tanımlı olmasına ihtiyaç duymaz. Tıpkı Bash Script’lerde olduğu gibi eğer tanımlı olmayan bir değişkenin değeri kullanılmaya çalışılırsa, CMake onun yerine boş bir String kullanır. Bu yaptığımız yeni açıklamlara göre aşağıdaki örnekleri inceleyelim:

set(myName mustafa) # myname = "mustafa"
set(mustafa2 x) # mustafa2 = "x"
set(test "${${myName}2}yz") # test = "${mustafa2}yz" = "xyz"
set(${mustafa2} a b c) # x = "a;b;c"
set(test2 ${notSet}) # test2 = ""
set(test3 "${notSet2}qwerty") # test3 = "qwerty"

Örnekler zaten kendi kendilerini açıklamaktadırlar. Çift tırnak ile bir String değeri atarken, bir alt satıra inip yeni satıra geçme karakterini de değere dahil edebilirsiniz. Ayrıca CMake String içerisinde C ve benzeri dillerde olduğu gibi çeşitli Kaçış Karakterlerini (Escape Characters) destekler. Aşağıdaki örnekleri inceleyin:

set(myTest "Mustafa
Yemural") # Value: Mustafa
          # Yemural
set(myTest2 "\"Mustafa\"\tYemural") # Value: "Mustafa"        Yemural

CMake 3.0’dan sonra çift tırnak desteğine alternatif olarak Lua Script’lerinde yer alan parantez sistemi getirilmiştir. Bu sisteme göre değişkenin değerini "[=[" ve "]=]" arasına yazabiliriz. Böylelikle değer kısmına tırnaklar ve boşluklar da dahil olmak üzere her şeyi yazabiliriz. Burada parantezler arasındaki "=" sayısı birden fazla olabilir veya hiç "=" karakteri olmayabilir. Ancak parantezin başında kullandığınız "=" karakteri sayısı ile sonunda kullandığınız sayı birbirine eşit olmalıdır. Bu gibi bir yazım biçimi genellikle değişkenlerin içine gömülü Script’ler atarken kullanışlı olur. Aşağıdaki örnekleri inceleyelim:

set(multi [[
Mustafa
Yemural
]])
set(bashScript [=[
#!/bin/bash
if [ -n "${FILE}" ]
    echo "${FILE} is exist!"
fi
]=])

Gördüğünüz gibi tırnaklar, boşluklar ve özellikle de "${…}" gibi değişken kullanım ifadelerini rahatlıkla bu parantezler arasında kullanabiliyoruz. Aksi takdirde bunların her biri için birer kaçış karakteri kullanmak zorunda kalacak ve belki de kodumuz okunamaz bir hal alacaktı. Buradaki "=" sayısının olayı, yine değer kısmında kullanılan "[]" parantezleri ile bizim sınırlarımızın karışmaması içindir. Genellikle bir veya iki "=" bizim işimizi görür. Daha fazlası yine göz yormaktan başka bir işe yaramayacaktır.

Değişkenleri Silmek

Normal değişkenler ile ilgili son bahsedeceğimiz konu ise, tanımladığımız değişkenlerin tanımını belli bir noktadan sonra silme (unset) konusu olacaktır. Bunun için CMake’teki unset komutunu kullanırız. Bu komuta basitçe sileceğimiz değişkenin ismini vererek işlemi gerçekleştirebiliriz. Bu komut kullanıldıktan sonra sildiğimiz değişkeni kullandığımız yerlerde artık boş bir String alırız (tıpkı tanımlamadığımız değişkenleri kullanmaya çalıştığımızda olduğu gibi). Şöyle bir örnek ile bütün olayı anlayacağınızı düşünüyorum:

set(testVar ab)
set(testVar2 ${testVar}) # testVar2 = "ab"
unset(testVar)
set(testVar3 ${testVar}c) # testVar3 = "c"

Sistemdeki Ortam Değişkenlerini Ele Almak

CMake ayrıca sistemde bulunan ve Ortam Değişkenleri (Environment Variables) adı verilen özel değişkenleri alma ve değiştirme yeteneğine de sahiptir. CMake’te bu değişkenlerin değerlerini elde etmek için $ENV{name} yazımı kullanılır. Onların değerlerini yine set komutuyla değiştirmemiz mümkündür. Örneğin çoğu sistemde bulunan ve arama yollarını içerisinde tutan PATH isimli değişkene yeni bir yol (path) değeri eklemek istediğimizde şöyle bir kullanım gerçekleştirebiliriz:

set(ENV{PATH} "$ENV{PATH}:/home/mustafaym/bin")

Gördüğünüz gibi buradaki tek fark değişken adını ENV{} içine yazmamız oldu. Değer kısmında ise bu ortam değişkeninin eski değerini elde edip sonuna bizim istediğimiz yol değerini ekliyoruz. Yalnız burada dikkat etmeniz gereken önemli bir husus var: Ortam değişkenlerinde yapılan değişiklikler sadece o anki CMake oturumunuzu kapsar. CMake çalışmayı bitirdiğinde ortam değişkeninde yaptığınız bu değişiklik eski haline döner. Bu değişim derleme zamanında da görülmeyeceğinden, ortam değişkenlerinin "CMakeLists.txt" dosyasında bu şekilde değiştirmek pek kullanışlı olmaz. Ancak onun değerini kullanacağınız zaman $ENV{name} kullanımı oldukça önemli bir hale gelir.

Cache Değişkenleri

Şimdi değişkenler ile alakalı asıl kritik konumuza geçelim. Yukarıdaki değişken tiplerine ek olarak CMake’te Cache Değişkeni (Cache Variable) adı verilen özel bir değişken türü bulunmaktadır. Bu değişkenler diğerleri gibi sınırlı bir ömre sahip değillerdir. Bu değişkenler daha önce bahsettiğim Build dizininde oluşan bir dosya olan "CMakeCache.txt" dosyası içerisinde tutulurlar. Ayrıca o projede CMake her çalıştırıldığında bu değişkenler o dosyada kalırlar. Yani bu değişkenlere ilk defa bir değer verildiğinde, onları bir şekilde silmediğiniz sürece "CMakeCache.txt" dosyasında tutulurlar. Cache değişkenlerinin değerleri normal değişkenler gibi elde edilse de set komutu ile tanımlanmaları normal değişkenlerden farklıdır:

set(<variable> <value>… CACHE <type> <docstring> [FORCE])

Burada yer alan "<variable>" ve "<value>" kısımları normal değişken tanımında olduğu ile aynıyken, CACHE anahtar kelimesi bu değişkenin bir Cache değişkeni olduğunu bildirir. Cache değişkenlerine değerlerin yanında ek bilgilerin de verilmesi mümkündür. Burada gördüğünüz ve birazdan bahsedeceğim "<type>" ve "<docstring>" kısımları bu ek bilgileri belirtir. FORCE ise Cache değişkeninin, varolan değerinin üzerine yazılması gerektiğini bildirir. Yine bunu ileride açıklayacağım.

Öncelikle "<type>" kısmı ile başlayalım. Buraya tıpkı diğer programlama dillerinde olduğu gibi değişkenin tür bilgisini yazabilirsiniz. Buraya şu kelimelerden birini yazabilmeniz mümkündür:

  • BOOL: Cache değişkeninin bir tür Boolean değişkeni olduğunu bildirir. Bu değişkenin değeri herhangi bir Boolean türü bildiren String ifadesi olabilir ("ON/OFF", "TRUE/FALSE", "1/0", gibi).
  • FILEPATH: Cache değişkeninin dosya sisteminde bulunan bir dosyanın yol (path) bilgisini tutan bir değişken olduğunu bildirir.
  • PATH: Cache değişkeninin dosya sisteminde bulunan bir dizinin yol bilgisini tutan bir değişken olduğunu bildirir.
  • STRING: Cache değişkeninin String tutan bir değişken olduğunu bildirir.
  • INTERNAL: Cache değişkeninin kullanıcıyı ilgilendiren bir değişken olmadığını, "CMakeLists.txt" dosyasında içsel işlemler için kullanılacağını bildirir.

Şimdi "Hani her şey String’di nereden çıktı bu tür bilgileri?" gibi bir soru sorabilirsiniz. Buraya yazılan tür bilgisi aslında değişkende yapısal olarak herhangi bir değişiklik yapmaz. Bildiğiniz gibi daha önce CMake’in GUI araçlarından bahsetmiştim. İşte buradaki "<type>" ve "<docstring>" kısımlarına yazdığınız değerler tamamen bu GUI aracında o değişkenin görünümü ile ilgilidir. "<type>" kısmı o değişkenle ilgili kullanıcıya gösterilecek olan kontrol çeşidini belirlerken, "<docstring>" kısmı ise o değişkenle ilgili GUI ekranında gösterilecek olan kısa açıklama yazısını belirler. "<docstring>" kısmına değişkenin açıklaması bir String şeklinde girilebilir veya boş bir String de girilebilir.

CMake GUI Örnek Kontroller
Figür 1: CMake GUI Örnek Kontroller

Örneğin yukarıdaki figürde OpenCV kütüphanesi için Configure aşaması geçildikten sonra gelen değişkenlerin listesi görülmektedir. Bu değişkenlerin değerlerinin düzenlendiği Value kısmında, bazı değişkenlerin düz metin değeri aldıklarını, bazılarına ise Checkbox kontrolü ile Boolean değeri verildiğini görebilirsiniz. İşte değer sütunundaki bu kontroller, o değişkenin "<type>" kısmına yazılan değer ile belirlenir. Ayrıca mouse ile Atlas_BLAS_LIBRARY gibi bir değişkenin üstüne geldiğinizde "Path to a library." gibi kısa bir açıklamanın belirdiğini görürsünüz. Bu açıklama da "<docstring>" kısmına girdiğiniz değerdir.

Eğer bir Cache değişkenini BOOL olarak tanımlarsanız, yukarıdaki gibi bir Checkbox kontrolü elde eder ve değişkenin değerini de bu kontrol ile ayarlarsınız. Eğer değişkenin türünü FILEPATH veya PATH yaparsanız, GUI aracı bu değişkenin değerini değiştirmek için size bir File Dialog kontrolü sunar. FILEPATH yaptığınızda bu kontrol dosyaları aramak ve seçmek için çıkarken, PATH yaptığınızda ise bu kontrol dizinleri aramak ve seçmek için çıkar. Bu iki tür arasındaki temel fark budur. Eğer bir Cache değişkenini STRING türünde tanımlarsanız, GUI aracı size serbest yazım yapabileceğiniz bir yazı kutusu desteği sağlar. INTERNAL türünde tanımlanan değişkenler ise GUI aracında gözükmezler.

Hemen küçük bir örnek yapıp bu anlattıklarımızın nasıl göründüğüne bakalım. Öncelikle aşağıdaki kodları içeren bir "CMakeLists.txt" dosyamız olsun:

cmake_minimum_required(VERSION 3.10)
project(TestProject LANGUAGES CXX)
set(boolTest TRUE CACHE BOOL "This is bool test.")
set(stringTest "Initial Value" CACHE STRING "This is string test.")
set(stringTest2 "Default" CACHE STRING "") # docstring yazmadan tanımlama
set(filepathTest "/bin/cat" CACHE FILEPATH "This is filepath test.")
set(pathTest "/usr/lib" CACHE PATH "This is path test.")
set(internalTest "Test" CACHE INTERNAL "")
add_executable(Test main.cpp)

Bu kodlar ile oluşturulan bir proje için CMake GUI’sinde gerekli ayarlamaları yapıp "Configure" butonuna bastığımızda ve Generator’ı belirlediğimizde, aşağıdakine benzer bir ekran elde ederiz:

Kod Örneğinin GUI Ekranı
Figür 2: Kod Örneğinin GUI Ekranı

Buradaki ilk iki değişkene şimdilik aldırış etmeyin. Bunları daha sonra açıklayaağım. Resimden görmeniz pek mümkün olmasa da tanımladığımız her bir değişken türü için CMake ayrı bir kontrol sunmaktadır. Örneğin stringTest ve stringTest2 değişkenlerinin değerlerini istediğiniz gibi değiştirebilirsiniz. pathTest değişkeninin değerini değiştirmeye kalktığınızda, yanda bir üç nokta (…) butonunun çıktığını göreceksiniz. Buna tıkladığınızda bir dizin seçme diyaloğu gelir. Aynı şey filepathTest değişkeni için de geçerlidir. Ancak onda dizin değil, dosya seçme diyaloğu gelir. boolTest değişkeninin değerini ise Checkbox yardımıyla değiştirebilirsiniz. internalTest değişkeninin GUI ekranında görünmediğine dikkat edin.

option Komutu

BOOL türünde Cache değişkenleri tanımalamk için CMake bize set komutunun yanında option isimli bir komut daha sağlamaktadır. Bu komutun genel formu şöyledir:

option(<variable> “<help_text>” [value])

Eğer bu komuta bir "[value]" değeri girilmezse, o değişkenin değeri varsayılan olarak OFF yapılır. Buradaki "<help_text>" kısmı ise set komutundaki "<docstring>" kısmı ile aynı görevi görür. Aslında option kullanımı set kullanımının biraz daha basite indirgenmiş halidir. Yani aşağıdaki iki komut aslında aynı görevi görektedir ve GUI ekranında bize bir Checkbox kontrolü gösterirler:

option(varName "Test variable." TRUE)
set(varName TRUE CACHE BOOL "Test variable.")

FORCE Anahtar Kelimesi

Normal değişkenler ile Cache değişkenleri arasında set komutunun kullanılması ile ilgili önemli bir fark da set komutunun varolan bir normal değişken için kullanıldığında, direk o anki değerin üzerine yazım yapmasıdır. Ancak Cache değişkenleri bu mantıkla çalışmaz. Eğer set ile bir Cache değişkeni ilk kez tanımlanıyorsa, bu durumda bu değer bu değişkenin değeri olur. Ancak varolan bir Cache değişkeni için set komutu kullanılıyorsa, normalde bu ikinci set kullanımı görmezden gelinir ve değişkenin ilk değeri tutulmaya devam edilir. İşte FORCE bunu değiştirmeye yarar.

option komutunda ise FORCE anahtar kelimesi bulunmaz. Bu komutun set ile olan bir farkı da budur. Eğer projede aynı isimle hem bir normal değişken hem de bir Cache değişkeni bulunuyorsa, burada normal değişken önceliklidir ve Cache değişkeninden önce normal değişken işleme alınır (elbette çağırma sırasına bağlı olarak değişiklik olabilir). Ancak bir Cache değişkenini FORCE ile tanımladığımızda, bu sefer Cache değişkeni varolan normal değişkene üstünlük sağlayacak ve normal değişken silinecektir. Ancak bu durum oldukça tehlikelidir. Çünkü ilk ve bir sonraki CMake çalıştırmasında farklara yol açabilir. Şimdi söylediklerimiz ile ilgili genel örnekler verelim:

set(test val)
set(normalVar ${test}) # normalVar = "val"
set(test val2 CACHE STRING "Test docstring.") # test = "val2"
set(normalVar ${test}) # İlk Çalıştırma: normalVar = "val2"
                                     # Sonraki Çalıştırmalar: normalVar = "val"
set(test val3)
set(normalVar ${test}) # normalVar = "val3"
set(cacheVar val4 CACHE STRING "Test docstring.") # cacheVar = "val4"
set(cacheVar val5 CACHE STRING "Test docstring.") # cacheVar = "val4" (değişiklik yok)
set(cacheVar val6 CACHE STRING "Test docstring." FORCE) # cacheVar = "val6"

Yukarıdaki kod biraz karmaşık olsa da dikkatli bakıldığında kendi kendini açıklamaktadır. Kodda tanımladığımız test değişkeninin ilk değeri val olacaktır. Ancak ondan sonra yapılan aynı isimli bir Cache değişkeninden dolayı, Cache değişkeninin değeri olan val2 değerini alacaktır. Bu yalnızca Cache değişkenlerinin oluşturulduğu ilk çalıştırmada geçerli olur. Sonraki çalıştırmalarda ise test değişkeninin değeri yine val olacaktır.

Yine FORCE kullanımındaki durumu da bu örnekten görmekteyiz. cacheVal değişkeninin değeri asla val5 olmaz. Ancak aynı isimli Cache değişkenini FORCE ile tanımladığımızda, bu sefer o değişkenin değeri en son FORCE ile tanımlanan değer olacaktır.

Bütün bu kuralları size genel açıklama yapmak amacıyla veriyorum. Normalde projelerde böyle karmaşık durumlar oluşturmaktan mümkün olduğunca uzak durulur. Çünkü her zaman her kuralı hatırlamayabiliriz ve bu da çeşitli zaman kayıplarına yol açar. Bu yazıda CMake’te yer alan değişkenlerin (sözde) türlerine, onları nasıl tanımlayabileceğimize ve Cache değişkenlerinin GUI aracına olan etkisine değindik. Aslında Cache değişkenleri ile alakalı söyleyeceğimiz daha çok şey olsa da bunları bir sonraki yazımda anlatmayı düşündüm. Ayrıca bu yazıda CMake’te hata ayıklamayı (debugging) ve çeşitli mesajları ekrana nasıl yazdırabileceğimizi de anlatacağım.

5 6 votes
Article Rating
Subscribe
Bildir
guest

0 Yorum
Eskiler
En Yeniler Beğenilenler