References
- Professional CMake : A Practical Guide
Contents
- Variables Basics
- Environment Variables
- Cache Variables (by using set, option, and Command Line)
- Debugging Varables and Diagnositics
이번 글에서 CMake의 기초적인 부분 중 하나인 변수 사용에 대해서 알아보겠습니다.
Variable Basics
CMake에서 변수를 정의하는 가장 기본적인 방법은 set() 명령어를 사용하는 것입니다. 변수는 다음과 같이 CMakeLists.txt 파일에 정의할 수 있습니다.
varName은 변수의 이름입니다. 이 이름은 영문자나 숫자, '_'(underscore)를 포함할 수 있으며, 영문자의 경우에는 대소문자를 구별합니다. 변수 이름에는 '.', '/', '-', '+' 도 포함할 수 있는데, 실제로는 거의 사용되지 않습니다. 간접적인 수단을 통해 다른 문자들도 가능하지만, 일반적으로 사용되지 않습니다.
다른 프로그래밍 언어에서 변수들이 특정 함수, 파일 등으로 제한된 범위를 갖는 것과 마찬가지로 CMake에서도 변수에는 사용할 수 있는 범위가 있습니다. 변수는 해당 범위 안에서만 읽고 쓸 수 있습니다. 다른 언어에 비해 변수의 범위가 좀 더 유연합니다.
이번 글에서 변수 범위에 대해서는 자세히 다루지 않고, 기초적인 것들을 다 살펴보고 변수 범위에 대해 따로 살펴보도록 하겠습니다.
CMake에서는 모든 변수를 문자열로 취급합니다. 다양한 문맥상에서 변수는 다양한 타입으로 해석될 수는 있지만, 궁극적으로 변수는 단지 문자열입니다. 변수값을 설정할 때 값에 공백이 포함되지 않는 한 해당 값을 따옴표(")로 묶을 필요는 없습니다. 만약 여러 값이 설정되면 변수값은 각 값을 구분하는 세미콜론(;)으로 결합됩니다.
아래는 변수값을 설정하는 여러가지 유형입니다.
set(myVar a b c) # myVar = "a;b;c"
set(myVar a;b;c) # myVar = "a;b;c"
set(myVar "a b c") # myVar = "a b c"
set(myVar a b;c) # myVar = "a;b;c"
set(myVar a "b c") # myVar = "a;b c"
변수값은 '${myVar}'을 사용하여서 얻을 수 있고, 문자열이나 변수가 필요한 곳이면 어디에서나 사용할 수 있습니다.
CMake에서는 재귀적으로 사용하거나 또는 설정할 다른 변수의 이름을 지정할 수도 있다는 점에서 매우 유연합니다. 또한, CMake는 변수를 사용하기 전에 정의할 필요가 없습니다. 만약 정의되지 않은 변수를 사용하면 Unix 쉘 스크립트와 마찬가지로 오류나 경로없이 빈 문자열로 대체됩니다.
set(foo ab) # foo = "ab"
set(bar ${foo}cd) # bar = "abcd"
set(baz ${foo} cd) # baz = "ab;cd"
set(myVar ba) # myVar = "ba"
set(big "${${myVar}r}ef") # big = "${bar}ef" = "abcdef"
set(${foo} xyz) # ab = "xyz"
set(bar ${notSetVar}) # bar = ""
문자열은 단일 행으로 제한되지 않으며 개행문자('\n')를 포함할 수 있으며, 백슬래시(\)를 사용하여 따옴표도 포함할 수 있습니다.
set(myVar "goes here")
set(multiLine "First line ${myVar}
Second line with a \"quoted\" word")
CMake 3.0 이상을 사용하는 경우, 따옴표 대신 대괄호를 사용할 수 있습니다. (bracket syntax)
(content의 시작이 '[=['로 시작되고 끝이 ']=]'로 표시되는 Lua에서 따옴, 참조 link)
대괄호 사이에 '='가 몇 개가 오는 것은 상관없지만 시작과 끝에는 같은 수의 '='를 사용해야 합니다.
그리고 여는 대괄호([) 뒤에 바로 개행 문자가 오는 경우 첫 번째 개행은 무시되지만, 뒤에 나오는 개행들은 무시되지 않습니다. 또한, 대괄호로 묶인 내용에서는 CMake 변수 참조가 수행되지 않습니다.
# Simple multi-line content with bracket syntax,
# no = needed between the square bracket markers
set(multiLine [[
First line
Second line
]])
# Bracket syntax prevents unwanted substitution
set(shellScript [=[
#!/bin/bash
[[ -n "${USER}" ]] && echo "Have USER"
]=])
# Equivalent code without bracket syntax
set(shellScript
"#!/bin/bash
[[ -n \"\${USER}\" ]] && echo \"Have USER\"
")
위 예시처럼 대괄호는 Unix 쉘 스크립트와 같은 내용을 작성할 때 적합합니다.
마지막으로 변수는 unset()을 호출하거나 변수에 대한 값 없이 set()을 호출하여 설정된 값을 해제할 수 있습니다.
아래는 동일한 동작을 수행합니다.
set(myVar)
unset(myVar)
Environment Variables
CMake는 환경변수의 값을 읽고 설정할 수 있습니다. 환경변수의 값은 $ENV{varName} 을 사용하여 가져오고, ${varName}을 사용할 수 있는 모든 곳에서 사용할 수 있습니다. 환경변수를 설정하는 것은 ENV{varName}을 사용하는 것을 제외하고는 일반 변수와 동일하게 설정할 수 있습니다.
set(ENV{PATH} "$ENV{PATH}:/opt/myDir")
이렇게 환경변수를 설정하면 실행 중인 CMake 인스턴스에만 영향을 영향을 미칩니다. 즉, CMake 실행이 완료되는 순간, 환경변수에 대한 변경된 부분들이 소멸됩니다. 따라서 이와 같이 CMakeLists.txt 파일 내에서 환경 변수를 설정하는 것은 별로 소용이 없습니다.
Cache Variables
CMake는 Cache(캐시)라는 변수도 지원합니다. 수명이 CMakeLists.txt 파일 처리 중으로 제한되는 일반 변수와는 달리 캐시 변수는 빌드 디렉토리의 CMakeCache.txt 라는 특수한 파일에 저장되며 CMake가 실행 간에 수명이 유지됩니다.
캐시 변수가 일단 설정되면 캐시에서 명시적으로 제거될 때까지 해당 캐시 변수는 설정된 상태로 유지됩니다. 캐시 변수의 값은 일반 변수가 동일한 방법으로 참조할 수 있지만(${myVar} 형식), set() 명령은 조금 다릅니다.
set 명령으로 캐시 변수를 설정하기 위해서는 CACHE 키워드를 추가해야합니다. 그러면 일반 변수 대신 varName이라는 캐시 변수로 적용됩니다. 캐시 변수에는 일반 변수보다 많은 정보가 포함되어 있는데, 바로 변수 타입(nominal type)과 documentation string을 포함하고 있습니다. 캐시 변수를 설정할 때 반드시 둘 다 설정해야하지만, docstring은 비어있을 수 있습니다.
물론 type이나 docstring이 CMake가 변수를 처리하는 방식에 영향을 미치지는 않지만, 만약 GUI tool을 사용한다면 유저에게 더 적합한 방식으로 변수를 제공하는데 사용됩니다. 즉, CMake를 실행하는 동안에는 캐시 변수 또한 항상 문자열로 취급하는데, 정의되는 type과 docstring은 GUI tool에서 사용자 편의를 향상시키기 위해서 사용됩니다.
type은 아래 중의 하나로 설정될 수 있습니다.
- BOOL : bool type으로 캐시 변수를 설정합니다. 변수의 값은 CMake가 부울을 문자열로 나타내는 방식 중에 하나를 따르면 됩니다. (ON/OFF, TRUE/FALSE, 1/0, etc...)
- FILEPATH : 캐시 변수가 디스크의 file path라는 것을 의미합니다.
- PATH : FILEPATH와 유사하게, 캐시 변수가 directory path라는 것을 의미합니다.
- STRING : 변수가 임의의 문자열로 처리됩니다.
- INTERNAL : INTERNAL로 설정하면 사용자가 사용할 수 없는 변수가 됩니다. Internal 캐시 변수는 때때로 지속적으로 프로젝트 내부 정보를 기록하는데 사용됩니다. (GUI tool 에서도 INTERNAL 변수는 표시하지 않습니다.)
GUI Tool에서 일반적으로 docstring은 캐시 변수에 대한 설명이나 변수가 선택될 때 짧은 한 줄의 설명으로 사용됩니다.
FORCE는 강제로 CACHE 변수를 설정하는 것을 의미합니다.
일반 변수와 캐시 변수의 중요한 차이점은 set() 명령어가 항상 기존 값을 덮어쓰는 일반 변수와 달리 캐시 변수는 FORCE 키워드가 있는 경우에만 set() 명령이 캐시 변수를 덮어쓰게 됩니다.
set() 명령으로 캐시 변수를 정의할 때는 set-if-notset 으로 동작하는데, 즉, 정의할 변수가 set되어 있지 않아야 set이 됩니다. 이는 FORCE 기능이 없는 option() 명령도 동일합니다.
(주로 개발자를 위해 사용됩니다. CMakeLists.txt 파일을 편집하지 않고도 GUI 도구 또는 스크립트로 값을 재정의할 수 있습니다.
일반 변수와 캐시 변수 사이에서 조금 헷갈릴 수 있는 부분은 두 변수가 별개라는 점입니다. 즉, 이름은 같지만 다른 값을 가지는 일반 변수와 캐시 변수가 있을 수 있습니다. 이런 경우에 CMake에서 ${myVar}을 사용할 때, 캐시 변수가 아닌 일반 변수의 값을 참조합니다. 다시 말하자면, 일반 변수가 캐시 변수보다 더 우선시됩니다.
이에 대한 예외는 캐시 변수 값을 설정할 때, 캐시 변수가 set()을 호출하기 전에 존재하지 않았거나, FORCE 옵션이 사용된 경우 현재 범위의 모든 일반 변수가 제거된다는 점입니다. 하지만, 이것은 처음 CMake를 실행했을 때에는 캐시 변수가 존재하지 않았지만, 두 번째 실행부터는 캐시 변수가 존재하기 때문에 처음 실행했을 때와 다른 결과를 얻을 수 있음을 의미합니다.
예제를 한 번 살펴보겠습니다.
set(myVar foo) # Local myVar
set(result ${myVar}) # result = foo
set(myVar bar CACHE STRING "") # Cache myVar
set(result ${myVar}) # First run: result = bar
# Subsequent runs: result = foo
set(myVar fred)
set(result ${myVar}) # result = fred
첫 번째 실행과 그 다음 실행에서 중간 myVar 값 출력이 변경됩니다. 또한 마지막 결과는 ${myVar}가 일반 변수인지 캐시 변수인지에 관계없이 myVar에 할당된 마지막 값을 참조하게 됩니다.
option() 명령어 사용
만약 BOOL 캐시 변수를 설정하는 것은 set() 대신 option() 명령어를 사용할 수도 있습니다.
option은 위의 형태로 사용할 수 있고, initialValue를 생략하면 OFF가 기본값으로 사용됩니다. initialValue는 set() 명령에서 허용하는 BOOL 값 중 하나를 따라야하고, 참고로 위 커맨드는 아래와 동일합니다.
Command Line에서 Cache Value 설정
cmake에 전달된 옵션을 통하여 캐시 변수를 직접 조작할 수 있습니다.
'-D' 옵션을 통해 가능하며, 이 옵션을 통하여 캐시 변수의 값을 정의할 수 있습니다.
cmake -D myVar:type=someValue ...
someValue는 myVar 캐시 변수의 이전 값을 덮어쓰거나, 새로 정의합니다. 이 동작은 본질적으로 CACHE 및 FORCE 옵션과 함께 set() 명령어를 사용하여 캐시 변수를 설정하는 것과 동일합니다.
이렇게 cmake 명령을 통해서 실행하게 되면 캐시에 저장되므로, cmake를 실행할 때마다 해당 캐시 변수 값을 지정할 필요는 없습니다. 변경되지 않는다면 한번만 -D 옵션을 추가하여 실행하면 됩니다. 한 번에 둘 이상의 캐시 변수를 설정하려면 -D 옵션을 여러번 추가해주면 됩니다.
이 방식으로 캐시 변수를 정의하면, CMakeLists.txt 파일 내에서 설정할 필요가 없습니다.
또한, 이 방식으로 정의된 캐시 변수는 빈 docstring을 갖습니다.
type을 생략할 수도 있는데, 이 경우에는 캐시 변수가 INTERNAL과 유사한 특수한 type이 됩니다.
cmake -D foo:BOOL=ON ...
cmake -D "bar:STRING=This contains spaces" ...
cmake -D hideMe=mysteryValue ...
cmake -D helpers:FILEPATH=subdir/helpers.txt ...
cmake -D helpDir:PATH=/opt/helpThings ...
위는 -D 옵션을 사용하는 다양한 예시를 보여주고 있습니다.
캐시 변수의 값에 공백이 포함되면 두 번째 줄처럼 -D 옵션 전체를 따옴표로 묶어야 합니다.
-U 옵션을 사용하면 캐시에서 변수를 제거하는 것도 가능합니다. 둘 이상의 변수를 제거하기 위해 필요에 따라 반복해서 사용할 수도 있습니다. -U 옵션에 *나 ?와 같은 와일드카드 문자를 사용하면 의도한 것보다 더 많은 변수를 삭제할 수 있기 때문에 유의해야합니다.
cmake -U 'help*' -U foo ...
+) cmake GUI tool로도 가능하지만 여기서 설명은 생략하도록 하겠습니다.
Debugging Variables and Diagnostics
message()
프로젝트가 복잡해지거나, 예기치 못한 동작을 조사할 때 CMake 실행 중에 진단 메세지와 변수 값을 출력하는 것이 유용합니다. 일반적으로 message() 명령을 사용하여 확인합니다.
message 명령어 둘 이상의 메세지를 입력할 수 있는데, 그렇게 되면 메세지1과 메세지2를 구분할 수 있는 구분자없이 단일 문자열로 함께 결합되어 출력됩니다. 따라서 일반적으로 공백을 유지하기 위해서 따옴표로 묶인 단일 메세지를 자주 사용합니다.
set(myVar HiThere)
message("The value of myVar = ${myVar}")
위 예시는 다음의 출력을 의미합니다.
The value of myVar = HiThere
message() 명령은 mode 키워드 옵션을 추가할 수 있는데, 이는 메세지가 출력에 영향을 미치고, 어떤 경우에는 에러와 함께 빌드를 중지시키기도 합니다. 사용되는 mode 는 다음과 같습니다.
- STATUS : 부가 정보를 의미하며, 출력되는 메세지 앞에 '--'이 붙어서 메세지가 출력이 됩니다.
- WARNING : CMake Warning을 출력합니다. CMake는 중지되지않고 계속 진행됩니다.
- AUTHOR_WARNING : WARNING과 유사하게 경고(dev)를 출력하지만, 개발자 경고가 활성화된 경우에만 표시됩니다. cmake 실행할 때, '-Wdev' 옵션을 주어야 활성화되고, '-Wno-dev' 옵션으로 비활성화시킬 수 있습니다. (자주 사용되지는 않습니다.)
- SEND_ERROR : 빨간색으로 강조 표시(지원하는 경우)되는 에러 메세지를 출력합니다. CMake가 Configuration 단계는 완료될 때까지 계속 진행되지만, Geranation은 수행되지 않습니다.
- FATAL_ERROR : hard error를 나타냅니다. SEND_ERROR 처럼 동일하게 메세지를 출력하고, CMake가 즉지 중지됩니다.
이 외에도 DEPRECATION, NOTICE, VERBOSE, DEBUG, TRACE 가 있지만, 저는 사용되는 걸 거의 본적은 거의 없습니다.
만약 mode 키워드없이 message 명령이 사용되면, 메세지는 중요한 메세지로 간주되어 어떠한 수정없이 출력됩니다.
variable_watch()
변수 디버깅을 위해서 제공되는 다른 명령에는 variable_watch() 가 있습니다.
이 명령어를 사용하면 변수를 읽거나 수정하려는 모든 동작들이 기록됩니다. command에는 변수를 읽거나 수정될 때마다 실행되는 명령을 지정할 수 있습니다. 이 명령은 CMake 함수 또는 매크로의 이름이어야하는데, CMake 함수나 매크로는 다음에 자세하게 알아보도록 하고 이번 글에서는 맛만 보도록 하겠습니다.
아래처럼 변수의 변화를 감지하고 실행할 command 함수 varWatch를 작성하고 cmake를 실행하면 ,
cmake_minimum_required(VERSION 3.10)
project(variable)
function(varWatch var access value list stack)
message("Varaible Name: ${var}")
message("Access: ${access}")
message("Variable Watch: ${value}")
endfunction()
variable_watch(myVar varWatch)
message(STATUS "Set myVar")
set(myVar HiThere)
message(STATUS "Read myVar")
message("The value of myVar = ${myVar}")
아래의 출력을 확인할 수 있습니다.
variable_watch는 잘 사용되지 않으니, 그냥 이런게 있구나 정도만 알고 있어도 될 것 같습니다.
'CMake' 카테고리의 다른 글
[CMake] Flow Control - if (0) | 2021.10.30 |
---|---|
[CMake] Lists (0) | 2021.10.29 |
[CMake] String Handling (string 명령어) (0) | 2021.10.29 |
[CMake] Tutorial (2) - Library 추가 (0) | 2021.10.28 |
[CMake] Tutorial (1) - Start CMake (0) | 2021.10.28 |
댓글