@@
0@ 초보자를 위한 설명
1@파일 세팅 및 적용
2@CMake 구문 분석
+1@ 변수/리스트의 선언과 작동원리
+2@ 중요함수/기본탑재변수/함수&매크로만들기/문법적 함수
++1@함수
++2@변수
++3@사용자 지정 함수와 매크로
3@ 마치며
@@@
일단 깔라는 대로 다 깔았지? 이번 강좌는 CMake문법에 대해 설명하는 시간이야.
이 강좌뿐만 아니라 CMake에 대해서만 설명하는 강좌도 만들 것이지만, 이 강좌에서는 원하는 목적에 맞는 기능만을 차근차근 설명해 나갈거니까 유의해줘. CMake에 대해 더 알고 싶다면 ~A1~을 참조해줘.
쓰면서 느끼는 건데 쉽게 설명할려면 수정 여러번 해야할 듯....
@@0@ 초보자를 위한 조언
아무리 생각해봐도 지금 시점에, 난 초보자들에게 CMake를 쉽게 설명할 수 없어. 그래서 만약 이 글을 읽어봐도 하나도 이해를 못하겠다? 그렇다면 아래의 CMakeLists.txt의 내용을 복붙하는 걸 추천해. 복붙만 해도 기초적인 강좌는 전부 진행할 수 있어.
CMake는 특성상, 낮든 높든, 여러 수준의 C++지식을 골구루 알고 있어야 자유롭게 사용할 수 있거든. 초보자들은 CMake의 고급기능을 배울 때가 아니야.
@@1@ 파일 세팅 및 적용

이런 식으로 폴더와 파일을 생성해줘. 여기서 include 폴더 Header1.hpp, main.cpp 는 니가 원하는 대로 이름지어도 되고
헤더(.hpp or .h)확장자도 둘 h hpp 뭐든 상관없어. 그러나 CMakeLists.txt 는 내가 만든 파일명 그대로 파일을 만들어야 해.
이렇게 하고나서 CMakeLists.txt파일을 열면 끝!
( CMake는 exe나 dll, lib 등의 실행파일을 만들 때 반드시 실행파일 하나당 대응되는, .cpp 파일이 하나만 필요해 (일대일 대응). 그 파일을 기준으로 컴파일을 하거든,
예를 들어 너가 exe 파일 두 개를 만들고 싶다면 CMakelists 파일 내에서 사용하는 .cpp 파일은 두 개만 있어야해. )
( CMake는 오직 CMakeLists.txt만을 코드로서 해석해. 제목 철자 하나라도 틀리면 인식을 못하니까 조심해.)
@@2@ CMake 구문 해석.
아래부터 내가 설명하는 방법 외의 방법으로 사용하는 것에 대해서 나는 정상 작동을 보장할 수 없어. 내 책임아님
cmake_minimum_required(VERSION 3.24.0) set(EXE_PROJECT_NAME "HelloWorld") project(${EXE_PROJECT_NAME} VERSION 1.1 LANGUAGES CXX) set(Mingw64_Compiler "C:/mingw64/bin") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED on) set(CMAKE_CXX_COMPILER "${Mingw64_Compiler}/g++.exe") set(EXE_Sources "${CMAKE_CURRENT_SOURCE_DIR}/include/Header1.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp" ) add_executable(${EXE_PROJECT_NAME} ${EXE_Sources}) target_include_directories(${EXE_PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") |
이 코드를 복붙하고 난 뒤 파일을 저장해주면 자동으로 CMake가 구성되는데, 이때 반드시 build 폴더가 생겨야해. 안 생긴다면
이 gif를 따라해.
@@2@1@ CMake에서의 변수/리스트
일단 CMake가 변수/리스트를 어떻게 다루는지 알 필요가 있어.
CMake에서는 모든 것이 문자열이라고 봐도 무방해.
(\n(줄바꿈문자), \ (공백문자),\"(쌍따옴표),\;(세미콜론) 같은 경우엔 함수 내에서 문법적으로 해석되지 않아.
즉 그냥 문자로 취급돼. 그리고 message() 로 출력할 땐 줄바꿈문자는 줄바꿈으로, 나머지는 \만 때고 출력돼.)
그럼 변수/리스트 설정의 예를 들게.
set(MyVar "s s s") #[[ 변수설정 ]] message( MyVar ) message( ${MyVar} ) #결과는 각각 "MyVar" , "s s s" 출력 |
일단 알아야 할 것.
1. CMake는 줄 단위로 함수를 실행한다. (같은 줄에서 두 개 이상의 함수 실행이 불가하다.)
2. 한 줄 주석은 # 이고 여러 줄 주석은
#[[
내용
]]
이다.
3. set함수는 오른쪽 문자열을 왼쪽 문자열로 변수를 "담는다".
set(문자열 "담긴문자열") 이때 따옴표는 담긴문자열에 포함되지 않아.
4. message함수는 문자열을 출력한다. message(s\" ; s\; ; s\ ) -> "s" s; s "로 출력
5. ${문자열} 은 담긴문자열 로 치환된다.
예
set("Var" "j j j" "ss")
message(${Var}) # message(j\ j\ j;ss)와 같음. 변환과정은 아래 참조.
CMake는 중요하게 여기는 문자가 3가지 있어. (공백), "(쌍따옴표), ;(세미 콜론). 이제 각각의 문자가 어떻게 쓰이느냐에 따라 리스트의 결과가 어떻게 바뀌는지 알아보자.
연산 순서는 전처리, 공백, 쌍따옴표, 세미 콜론 단위의 해석 순서야.
1. 전처리 과정에서 가장 왼쪽 문자열과 가장 오른쪽 문자열의 바깥 쪽에 쌍 따옴표를 붙여.
이미 있을 경우는 생략해.
set(var1 s s s "s s s" "g ; g") - > set("var1 s s s "s s s" "g ; g")
2. 공백 과정에서는 문자열 사이에 공백이 하나 이상 있다면 그 공백(들)을 """" (쌍따옴표(쌍따옴표)로 치환해.
한쪽에 이미 쌍 따옴표가 있다면 그쪽은 생략하고, 쌍 따옴표 내의 공백은 "\ "(공백 문자, 문법적으로 해석하지 않음.)로 바꾸기만 해.
set("var1 s s s "s s s" "g ; g") -> set("var1""s""s""s""s\ s\ s""g\ ;\ g")
3. 쌍따옴표 과정에서는 가장 왼쪽과 가장 오른쪽의 따옴표를 제외하고 내부의 쌍 따옴표 쌍을 전부 ;으로 바꿔
그러면 전부 하나의 문자열이 되지.
set("var1""s""s""s""s\ s\ s""g\ ;\ g") -> set("var1;s;s;s;s\ s\ s;g\ ;\ g")
4. 이제 세미콜론 단위로 나눠진 문자열을 함수가 해석할 차례야. set함수의 경우는 가장 첫번째로 오는 문자열을 뒤에 오는 문자열을 담을 문자로서 해석해. 결국 "var1"을 ${}로 호출할 때 "s;s;s;s\ s\ s;g\ ;\ g"로 치환되지.
set("var1;s;s;s;s\ s\ s;g\ ;\ g"), "var1" 에 "s;s;s;s\ s\ s;g\ ;\ g"을 담아야지 ㅎㅎ.
message(${var1}) -> message(s;s;s;s\ s\ s;g\ ;\ g) -> "s s s s s s g g" 을 출력.
이제 ${}에 대해 더 자세히 알아보자. 이건 함수가 실행되기 전 가장 먼저 실행되고, 문자열에 담긴문자열로 치환돼.
예를 들어,
set("var1" "s s" "j;j")를 했을때,
message(${var1})을 하게 되면 "${var1}" 이 "s\ s;j;j"로 치환돼.
message(${var1}) = message(s\ s;j;j) #출력 "s sjj"
@@2@2@ CMake에서의 자주 쓰이는 함수 및 CMake의 기본탑재 변수.
@@2@2@1@ 함수
1.set ~1~
2.message ~2~
3.cmake_minimum_required ~3~
4.project ~4~
5.add_executable ~5~
6.include_libraries ~6~
7.link_libaries ~7~
8.include ~8~
9.set_target_properties ~9~
1.set *1*
set(문자열 담을문자열1 담을문자열2.....)
위에서 설명했듯이 왼쪽의 문자열에 오른쪽에 있는 문자열(들)을 담는다.
2.message *2*
message(문자열)
문자열을 출력한다.
3.cmake_minimum_required *3*
cmake_minimum_required(VERSION X.XX.X)
가장 상단에 있어야 하는 함수인데,
CMake의 버전마다 함수의 기능과 존재유무가 다르고, 하위호환은 지원하지만 상위호환은 지원하지 않기에,
CMake프로그램의 버전이 X.XX.X 보다 낮다면, 오류를 출력한다.
(만약 이 기능이 없다면, 코드 자체는 정상작동하는데 그 결과가 최신버전의 결과물과 매우 다를 수 있기에, CMake는 반드시 이 함수를 처음에 쓰도록 강제한다.)
VERSION은 뒤에 오는 문자열을 버전으로 해석하겠단 뜻이다.
X.XX.X 는 버전이다. 그냥 마음편하게 CMake의 현재버전 쓰면 된다.
4.project *4*
project(프로젝트명 VERSION X.X.X LANGUAGES CXX)
프로젝트를 만든다. 이 프로젝트는 exe, dll, lib과 cpp와 일대일 대응이다. 즉 한 CMakeLists.txt에서 여러 개의 프로젝트를 만들 수 있다.
프로젝트명 프로젝트의 이름이다. 아래에 설명된 함수들 내에서 쓰이게 된다.
가능한 예
"MyGame","Hello\n\ world","lasjf13ds_dfsf353sdjkljl"
VERSION 뒤에 올 X.X.X를 프로젝트의 버전으로 해석한다. (CMake의 버전이 아님!)
X.X.X 프로그램의 버전이다. 최대 네개의 숫자를 사용할 수 있고, ("X.X.X.X") 프로그래머 마음대로 지정할 수 있고, 한번 설정하면 이후 CMakeLists.txt 내부에서 변수로서 불러올 수 있다.
예 "1.1.1", "1432.234234123342.1324234324123", "1.1.1.1"
5.add_executable *5*
add_executable(프로젝트명 .h.hpp.cpp파일(들)의경로)
프로젝트를 exe로 컴파일하겠다는 선언과 동시에 exe로 컴파일하기 위한 코드들이 무엇인 CMake에 알려주는 거야.
사실 .cpp파일만 해줘도 되는데, 아래에 나올 target_include_libraries나 include_libraries함수에 헤더파일이 존재하는 폴더경로만
넣어줘도 알아서 헤더파일을 탐색해주거든. 자세한건 ~6~
6.include_libraries *6*
include_libraries(가져오고싶은헤더들이들어있는폴더경로들)
모든 프로젝트에 헤더경로를 추가해줘. 예를 들어서,
include_libraries("${CMAKE_CURRENT_SOURCE_DIR}/include")
를 해주면 "cpp파일과 그 파일에 포함된 모든 헤더"에서 #include <Header1.hpp>를 해줄 수 있어.
근데 이게 좀 작동방식이 특이해. a.h, main.cpp가 있다고 하자. 이때 main.cpp가 a.h를 include하지 않으면 a.h에서 인텔리센스가 작동하지 않아. 그런데 main.cpp에서 #include <a.h> 하고나면, CMake가 구성할 때 이걸 감지하고 a.h에 인텔리센스를 활성화해.
(main.cpp를 저장한 이후 CMakeLists.txt파일을 다시 한번 저장해서 재구성해주고 나서야 a.h에 인텔리센스가 적용됨 주의)
예를 들어, b.h -> a.h -> main.cpp의 포함관계가 있는 파일들과,그리고 단독인 c.h라는 파일을 만들었을 때, b.h, a.h, main.cpp는 인텔리센스가 적용되지만 c.h는 기본 라이브러리를 제외한 그 어떤 인텔리센스도 적용되지 않아.
7.link_libaries *7*
link_libraries(링크할정적라이브러리목록)
모든 프로젝트에 목록에 들어있는 정적라이브러리들을 링크해줘. 예를 들어서,
link_libraries("${CMAKE_CURRENT_SOURCE_DIR}/anyLib.a")
를 해주면 어떤 cpp에서든 anyLib.a가 링크돼. 링크에 대한 편은 나중에 소개할게, 지금은 신경안써도 돼.
8. include *8*
include(가져올CMake파일경로)
이건 부연설명이 필요해.
CMake는 CMakeLists.txt 뿐만 아니라 AnyName.cmake 확장자로도 코드를 작성할 수 있어.
그리고 이렇게 만든 .cmake 파일은 어느 CMakeLists.txt , 그리고 같은 확장자인 .cmake에서도 include함수로 불러올 수 있어.
작동방식은 그대로 복붙되는 거라고 생각하면 돼. 단! CMAKE_CURRENT_LIST_DIR ~2-2~ 변수를 AnyName.cmake 내에서 사용하는 경우, AnyName.cmake의 경로를 반환하니 유용하게 사용하면 돼. (당연하지만 순환 참조는 에러가 나.)
9.set_target_properties
set_target_properties(프로젝트이름"들"
ARCHIRVE_OUTPUT_DIRECTORY
"경로"
LIBRARY_OUTPUT_DIRECTORY
"경로"
RUNTIME_OUTPUT_DIRECTORY
"경로")
설정한 프로젝트들에 대해,
archirve와 library에서 설정한 경로엔 lib파일이,
runtime에서 설정한 경로에는 dll, exe가 들어가.
예)
set_target_properties(project1 project2 project3
ARCHIRVE_OUTPUT_DIRECTORY
"built/lib"
LIBRARY_OUTPUT_DIRECTORY
"built/lib"
RUNTIME_OUTPUT_DIRECTORY
"built/runtime")
#exe,dll 은 runtime
#lib은 lib
@@2@2@2@ 기본변수
1. CMAKE_CURRENT_SOURCE_DIR ~2-1~
2. CMAKE_CURRENT_LIST_DIR ~2-2~
3. CMAKE_PROJECT_NAME ~2-3~
4. CMAKE_CXX_STANDARD ~2-4~
5. CMAKE_CXX_COMPILER ~2-5~

1. CMAKE_CURRENT_SOURCE_DIR *2-1*
맨처음 실행시킨 CMakeLists.txt 의 경로야.
2. CMAKE_CURRENT_LIST_DIR *2-2*
지금 실행되고 있는 코드의 경로야. include()와 같이 쓰면 좋아.
3. CMAKE_PROJECT_NAME *2-3*
이 변수가 호출될 시점에서 가장 마지막으로 선언된 프로젝트의 이름이야. 그리고 중심이 되길 원하는 프로젝트를 설정해도 돼.
4. CMAKE_CXX_STANDARD *2-4*
어떤 c++의 표준을 기준으로 컴파일할지를 설정하는 거야.
예) set(CMAKE_CXX_STANDARD 20)
5. CMAKE_CXX_COMPILER *2-5*
컴파일러를 지정해.
@@2@2@3@ 함수와 매크로 만들기
CMake에선 사용자가 직접 함수를 만들 수도 있어.
함수와 매크로 두가지 방법이 있는데 두 개의 차이는 매개변수 뿐이야. 일단 함수/매크로의 정의방법을 보자
1.macro ~3-1~
macro(함수이름 매개변수1 매개변수2 .....)
message(${매개변수)
anyfunction......()
endmacro()
2.function ~3-2~
function(함수이름 매개변수1 매개변수2)
message(${매개변수)
anyfunction......()
endfunction()
이제 둘의 차이를 알아보자.
set으로 변수를 선언했을 때, macro는 내부의 모든 것이 전역이지만, (내부에 변수를 선언하면 밖에서 쓸 수 있고, 밖의 변수를 안에서 쓸 수도 있다.)
반면에 function안에서 이름을 선언하기
macro의 ${}는 특별취급 받아, 함수 실행 바로 전에 치환되는 게 아니라 macro가 실행되는 순간, macro내의 모든 ${}을 치환하지.
macro는 매개변수로 받은 문자열을 그대로 치환하고, function은 매개변수 이름에 문자열을 담아. 차이를 보여줄게.
1.macro *3-1*
set(ggg omg)
macro(mHello "arg")
message(${arg})
message(${ggg})
set(arg "goza")
set(ggg "gggay")
message(${arg})
message(${ggg})
endmacro()
message(${ggg})
mHello(hello)
#[[
=
message(hello)
message(${ggg})
set(arg "goza")
set(ggg "gggay")
message(hello)
message(${ggg})
]]
#출력 = hello omg hello gggay gggay
2.function *3-2*
set(ggg omg)
function(fHello "arg")
message(${arg})
message(${ggg})
set(arg "goza")
set(ggg "gggay")
message(${arg})
message(${ggg})
endfunction()
message(${ggg})
fHello(hello)
#[[
=
set(_arg hello)
#_(언더바)는 실제가 아닌 다른 변수 사용의 의미임.
message(${_arg})
message(${ggg})
#변수호출중, 함수내 지역변수에 ggg선언이 없으면 전역변수인 ggg가 호출됨.
set(_arg "goza")
set(ggg "gggay")
#ggg 선언 시 전역변수 ggg에 영향을 주지 않고 ggg가 함수내 지역변수에 추가됨. 이제 ${ggg}를 할 때 전역변수 대신
#지역변수 ggg가 쓰임.
message(${arg})
message(${ggg})
#지역변수 삭제, 외부공간에서 사용 불가.
]]
#출력 = omg hello omg goza gggay omg
나는 개인적으로 function을 추천해, 왜냐하면 전역변수를 바꿀 수 없거든. 아니면 매크로를 쓰고, 지역변수만의 규칙을 만들어 주는 것도 좋지. __localVar, 이런식으로.
@@3@ 마치며
만약 CMake에 대해서 더 알고 싶다면 인터넷의 망령이 되거나, 내가 만든 CMake강좌~A1~를 추천해, 그 강좌엔 기초적 코딩에 필요한 지식들이 다 있을거야. 원한다면 체계적인 프로젝트도 만들 수도 있고.
@@@
*A1* https://arca.live/b/programmer/60904136 CMake 아마도 곧 끝날 총정리.