CMake로 할 수 없는 것들
CMake도 장점과 단점이 존재한다. 그래도 여기서 언급되는 대부분의 단점들은 평소 워크플로우와는 조금 다른 방식으로 접근하면 우회해서 해결할 수 있다. 전체 그림을 놓고 생각해보면 CMake 장점이 단점보다 훨씬 더 크다.
CMake의 언어 및 문법 관점
CMake 언어는 다른 어떤 언어와도 쉽게 비교할 수 있는 종류의 언어는 아니다. 클래스, 맵, 가상 함수, 람다는 존재하지 않는다. 심지어 함수의 입력 매개변수들을 파싱하는 것이나 함수로부터 값을 반환하는 기본 작업 또한 쉽지 않다. 일반 프로그래밍 언어처럼 알고리즘 구현이나 JSON 응답 처리같은 기능을 구현할만한 언어는 절대 아니다. 하지만 일반적인 개발 작업을 다루는 데에는 아주 효율적이다.
만약, 비표준적인 행위를 CMake에서 하려고 한다면 하지 않는게 좋다. 진짜 필요하다면 CMake 자체를 고쳐서 컨트리뷰트 하자.
워크플로우에 영향을 미침
좋아하는 도구를 계속 써도 된다는 말과 모순처럼 들릴 수 있지만 그렇지는 않다. 어떤 IDE던 지속적으로 사용할 수 있지만, CMake가 총책임자임에 주의해야 한다. 자동 생성된 파일을 수정하는 일은 없도록 하자. CMake를 사용할 때는 IDE안에서 빌드 설정을 절대 직접 수정하면 안된다. CMakeLists.txt로부터 생성된 모든 타겟 파일들과 IDE에서 직접 추가한 모든 프로젝트 변경 사항들은 다음 번 CMake를 다시 실행하는 순간 모두 사라진다.
잘못된 워크플로우

예를 들어, Visual studio 솔루션에서 새로운 라이브러리를 추가하고 싶을 때 "Add → New Project → Visual C++ → Static Library" 같은 방식으로 추가하면 안된다. 반드시 `CMakeLists.txt`에서 `add_library()` 커맨드를 통해 라이브러리를 추가해야 한다.
올바른 워크플로우

기능 커버리지의 불완전
CMake의 기능과 네이티브 빌드 도구의 기능 사이에 대응 관계는 항상 1대1 대응인 것은 아니다. 이런 문제는 보통 같은 CMake 코드로부터 서로 다른 네이티브 빌드 파일들을 생성하는 방식으로 우회할 수 있다.
예를 들어, autotools(GNU의 빌드시스템)를 사용하면 한 번의 실행으로 두 가지 버전의 라이브러리(share + static)를 생성할 수 있다. 하지만 이런 방식은 성능에 영향을 주거나 어떤 플랫폼에서는 아예 불가능할 수 있다. (예: Windows) CMake를 사용하면, 하나의 `CMakeLists.txt`파일로부터 프로젝트를 두 번 생성해서 두 가지 버전을 만들 수 있다. (공유 라이브러리 한 번, 정적 라이브러리용 한 번) 즉, generate/build 단계를 두 번 돌리는 것이다.
Visual Studio를 직접 쓰면, 하나의 솔루션 파일안에서 x86, x64 두 가지 변형을 동시에 가질 수 있다. 하지만 CMake를 사용하면 프로젝트를 두 번 생성해야 한다. 한 번은 Visual Sutido 제너레이터로, 또 한 번은 Visual Studio Win64 제너레이터로 생성해야 한다. 즉, 플랫폼 / 설정 / 툴체인 별로 깨끗하게 분리된 빌드를 가져야만 한다.
재배치할 수 없는 프로젝트
내부적으로 CMake는 각 소스 파일의 전체 경로를 저장하기 때문에 한 번 프로젝트를 생성하고 그걸 여러 개발자에게 그대로 공유하는 것을 불가능하다. 다시 말해 한 사람이 CMake 담당자가 되어 Xcode 쓰는 사람용 프로젝트 하나를 만들고, Visual Studio 쓰는 사람용 프로젝트를 하나 만들어서 나누어 주는 방식은 성립하지 않는다.
그래서 팀에 있는 모든 개발자는 CMake를 사용해서 프로젝트를 생성하는 방법을 알고 있어야 한다. 즉, 실제로는 각자가 어떤 CMake 인자를 써야하는지를 알고 있어야 한다는 뜻이다. Xcode는 `cmake -H. -B_builds -GXcode` Visual Studio는 `cmake -H. -B_builds "-GVisual Studio 12 2013"`같은 명령어들을 예로 들 수 있다.
즉, 한 번 generate한 프로젝트를 여러 개발자에게 그대로 공유하는 것은 어렵다. 산출된 네이티브 빌드 시스템 파일에 작성되는 소스 코드이 경로는 하드코딩되고, 컴파일러, SDK 등 경로가 모두 다르기 때문. CMake 프로젝트에서 공유해야할 파일은 `CMakeLists.txt`, `toolchain file`, `preset.json`이고, 그 외 `.vcxproj`, `.xcodeproj`, `Makefile`, `build.ninja` 같은 것들은 각자 로컬에서 생성하는 캐시/산출물이다.
Visual Studio을 통해 생성된 .vcxproj과 CMake를 통해 생성된 .vcxproj를 비교해보았다. (프로젝트는 다르지만, 절대 경로와 상대 경로를 확인할 수 있다.)
Visual Studio을 통해 생성된 .vcxproj
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Game.cpp" />
<ClCompile Include="Main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Game.h" />
</ItemGroup>
CMake로 생성된 .vcxproj
<ItemGroup>
<ClCompile Include="D:\Development\Projects\lidar-visualization\build\src\core\core_autogen\mocs_compilation_Debug.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='MinSizeRel|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='RelWithDebInfo|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="D:\Development\Projects\lidar-visualization\src\core\player\PointCloudPlayer.cpp" />
<ClInclude Include="D:\Development\Projects\lidar-visualization\src\core\player\PointCloudPlayer.h" />
<ClCompile Include="D:\Development\Projects\lidar-visualization\src\core\player\VideoFramePlayer.cpp" />
<ClInclude Include="D:\Development\Projects\lidar-visualization\src\core\player\VideoFramePlayer.h" />
<ClCompile Include="D:\Development\Projects\lidar-visualization\src\core\viewer\OrbitCamera.cpp" />
<ClInclude Include="D:\Development\Projects\lidar-visualization\src\core\viewer\OrbitCamera.h" />
<ClCompile Include="D:\Development\Projects\lidar-visualization\src\core\repository\PointCloudRepository.cpp" />
<ClInclude Include="D:\Development\Projects\lidar-visualization\src\core\repository\PointCloudRepository.h" />
<ClCompile Include="D:\Development\Projects\lidar-visualization\build\src\core\core_autogen\mocs_compilation_Release.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='MinSizeRel|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='RelWithDebInfo|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="D:\Development\Projects\lidar-visualization\build\src\core\core_autogen\mocs_compilation_MinSizeRel.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='RelWithDebInfo|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="D:\Development\Projects\lidar-visualization\build\src\core\core_autogen\mocs_compilation_RelWithDebInfo.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='MinSizeRel|x64'">true</ExcludedFromBuild>
</ClCompile>
</ItemGroup>
어찌되었든, 자기 워크플로우를 어떻게 변경해야 할지도 이해하고 있어야 하고, 개발자는 자기가 사용하려는 코드가 어떤 도구로 만들어졌는지를 이해하고, 그 도구를 배우려는 노력을 해야한다고 한다.
오직 최종 사용자를 대상으로 완성 제품을 제공할 때만, 프로젝트 파일 대신 `*.msi` 같은 사용자 친화적인 설치 파일을 만들어 줄 책임이 나에게 있다.
CMAKE_USE_RELATIVE_PATHS 명령어는 CMake 3.4 이후로 제거되었다. 설명 미래에 상대 경로 지원이 다시 구현된다 하더라도, 팀의 모든 개발자는 CMake를 설치해두어야 한다. 왜냐하면 CMake가 자동으로 처리해주는 다른 작업들이 있는데, 그걸 수동으로 하면 실수할 가능성이 크기 때문이다. 예를 들면 다음과 같다:
- `CMakeLists.txt`가 변경되었는지 자동으로 감지하고 그에 맞게 소스 트리를 다시 생성해주는 기능
- 내장 스크립트 모드를 사용해서 사용자 정의 빌드 단계를 빌드 과정에 포함시키는 기능
- 설치된 의존 패키지들을 자동으로 찾아주는 것 같은 내부 처리 작업들
참고 자료
https://cgold.readthedocs.io/en/latest/overview/cmake-can-not.html