CMake Primer

강형우·2023년 2월 24일
0

CMAKE

목록 보기
1/1

CMake?

CMake

what is it?

  • CMake는 어떠한 플랫폼에서도 사용이 가능한 범용적인 소프트웨어이다.
    • window, mac, linux, android..
  • build 자동화, testing, packaging, installation까지 해준다.
  • 컴파일러와는 독립적인 작동 방식을 가지고 있다.(어떠한 컴파일러가 와도 잘 작동함)
    • 컴파일러? - 우리가 짠 c++ code를 machine code로 바꿔줌
  • CMake는 빌드 해주는 주체가 아니라 어떠한 시스템에도 맞는 빌드 파일을 생성해주는 역할을 한다.
  • 복잡한 폴더 구조도 풀어줄 수 있고, 여러 앱들마다 다양한 라이브러리를 사용할텐데, 이들 역시 구조적으로 풀어줄 수 있다.
  • 시스템이 자신의 시스템에 잘 맞는 방법들(e.g 우분투의 경우 Make, 안드로이드의경우 Android Studio, 맥의경우 xcode, windows에서는 visual studio..)
  • 실습에서는 주로 make 와 ninja를 사용할 것.

what to expect

  • Build 3rd Party libaries from souce
    • No pip-like system for C++
  • Make a C++ project
    • Load your header / source files
    • Load 3rdParty libraries
    • Configure output executables / libraries

[실습] Build 3rd Party

  • CMake 설치

  • 설치할 프로그램의 소스코드 가져오기(이번 강의에서는 OpenCV이용)

    • opencv가 cmake로 build가 가능한지는 어떻게 알 수 있을까?
      • 구조를 보면 상위단에 CMakeLists.txt가 있으면 CMake로 build를 지원함
  • 로컬 환경으로 다운로드

    • 이 과정에서 git이 없다면
      • sudo apt -get install -y git을 통해 git을 설치하도록 하자
  • Build를 해보자. CMake build에는 2가지 단계가 필요하다.

    • 첫번째로는 generate단계
      • 이 단계에서는 CMkaeLists.txt를 읽고 C++프로젝트가 어떻게 되어있는지 구조를 파악하고 그 다음 빌드를 위한 컴파일러가 존재하는지 build 시스템이 존재하는지 그리고 빌드에 필요한 모든 소스코드가 제자리에 있는지 확인한다. 3가지가 모두 존재하는지 확인이 되었다면 build command를 생성한다.
    • 두번째로는 build단계
      • 우리가 세팅한 build 시스템의 C++ 프로그램과 library 빌드를 수행한다.
  • clone이 끝났다면 home폴더에 opencv라는 폴더가 생겼다

  • opencv를 위한 build와 install folder를 만들자

    • opencv_build 폴더에는 build의 완성물들이 존재한다.
    • opencv_install 폴더에서는 완성된 build에서 유저가 직접 사용할 라이브러리와 프로그램들만 시스템폴더로 옮겨주는 작업이다. 보통은 system 폴더로 가기때문에 install 폴더를 만들어줄 필요는 없지만, 튜토리얼에서는 시스템 설치 방법을 알려주고, 원하는곳에 설치를 할 수 있는 방법을 보여주고자 install 폴더를 만들었다.
  • build폴더 안에 들어간다.

    • generate 단계를 할것인데, 가장 간단하게 쓸 수 있는 command는
      • cmake ../opencv
        • cmake 명령어를 통해서 cmake를 호출함.
        • 그 다음 parameter로 들어가는것은 generate하고싶은 project(이 경우 opencv)의 가장 상위단의 있는 CMakelist.txt파일이 존재하는 디렉토리 경로를 적어주면 된다. ..은 상위를 뜻함
  • 이 명령어만 쳐도 generate가 된다.

  • 더 보여주고 싶은 것은

      1. debug모드
      • C++에서 debug모드로 release를 하면 모든 코드의 최적화 과정을 풀어주어 프로그램 진행에 필요한 모든 진행 정보를 제공을 해준다. 이 덕분에 debugging을 할 때 충만한 정보로 debugging을 할 수 있다. 대신 코드 최적화가 안되어 있으니 엄청나게 느리게 돌아가고 메모리도 많이 잡아먹는다.
      1. release모드
      • release모드로 진행을 하면 코드 최적화가 많이 들어가있어 코드의 속도가 엄청나게 빠르게 돈다. 대신 메모리에 저장한 symbol들도 사라지기 때문에 debugging하기 어렵다는 단점이 생긴다.
  • CMake에서도 debug모드와 release모드를 선택할 수 있다. generate단계에서 option을 주면 된다.

  • cmake -D../opencv

    • -D를 주면 파라미터를 옵션으로 주겠다는 뜻이다.
  • cmake -DCMAKE_BUILD_TYPE=Debug ../opencv

    • cmake에 대한 build type은 debug를 할 것이다.
  • cmake -DCMAKE_BUILD_TYPE=Release ../opencv

    • cmake에 대한 build tpye을 release로 할 것이다.
  • Build System 고르는 방법

    • build system은 cmake에서 generate라고 한다.
    • generate를 고르는 방법
      • e.g) ninja로 build하는 방법
        • -G는 generator
        • cmake -DCMAKE_BUILD_TYPE=Release - GNINJA ../opencv
  • install prefix

    • install을 할 때 저장되는 위치를 선택하는것
    • 기본적으로 지금처럼 아무런 설정 없이 설치를 하면 cd/usr/include 이런곳에 있을 것.
      • window로 비유를 하자면 programfiles에 저장한다와 비슷하다고 보면 된다.
      • 이곳에 설치하면 좋은점
        • 어떠한 프로젝트에서도 로딩을 할 때 쉽게 라이브러리를 로딩할 수 있다.
      • 이곳에 설치했을때의 단점
        • 동일한 라이브러리를 여러버전으로 설치할 때 버전 충돌이 날 수 있다.(CMake에서 라이브러리 로딩을 할 때 특정버전을 읽을것이라고 명시해주는 방법이 있긴 하다)
        • 동일한 라이브러리를 동일 버전으로 대신 다른 빌드 설정으로 시스템에 빌드를 했다 라고 한다면 100% 꼬이게 된다. 이 경우 우분투를 밀어야 할 수도..
    • 따라서 system directory가 아닌 local folder에 라이브러리를 설치해서 보관을 하는것을 추천
      - 장점: 꼬이는것을 피할 수 있다, 지울때도 폴더만 지우면 된다.
      - 원하는곳에 설치하는 방법
      - cmake -DCMAKE_BUILD_TYPE=Release -GNINJA -DCMAKE_INSTALL_PREFIX=../opencv_install ../opencv
    • 아직은 ninja가 안깔려 있으므로
      - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../opencv_install ../opencv
  • 실행결과

  • generate까지 되었으니 build폴더의 내부를 보자.

    • 여기서 눈여겨서 봐야 할 것은 Makefile
    • Makefile은 make build system이 사용할 수 있는 command들을 쭉 늘여트려놓은것
    • 내부를 살펴보자.
  • build를 해보자

    • 가장 간단한 방법은 make를 치는것이다.
    • generator가 make일때는 Makefile이 생기게 되는데 여기서 make를 치게 되면 자동으로 Makefile 기준으로 build가 실행이 된다.
      - -j라는 옵션이 있다. make -j -j는 동시에 빌드를 할 수 있는 작업의 수를 의미한다.
      - 보통 cpu코어 하나당 하나씩 할당이 되어 병렬 빌드를 함으로써 더 빠르게 빌드가 된다.
      - make -j2, make -j4 뒤의 숫자를 통해 원하는 동시 작업의 수를 적는다.
    • 4개의 코어가 있으므로 2개의 코어만 사용하자
  • 라이브러리가 빌드 되었다.(lib 폴더에)

    • 이대로 링크를 해서 C++ 프로젝트로 사용해도 괜찮다.
  • 하지만 opencv에서는 install 기능을 지원한다.

  • build 폴더에는 build 중간에 필요한 파일들이 있다. CMakeCache, CMakeDownloadLog...등등

  • 유저입장에서 opencv를 사용하는데 필요한건 아니기에 필요한 파일들만 따로 모아주는 install을 해보자

  • sudo make install을 통해 install이 가능하다.



    • 자동으로 opencv 링크를 편하게 해주는 cmake configfile들이 있다.
    • C++ 프로그램에 opencv를 링크할 수 있도록 하는 모든 파일들이 install폴더에 있는것을 확인할 수 있다.

CMake 커맨드

우리가 쓴 커맨드

[Generate 과정]

  • cmake_DCMAKE_BUILD_TYPE=Release -GNinja -DCMAKE_INSTALL_PREFIX=../install ../opencv
    • -DCMAKE_BUILD_TYPE=Debug/Release
      • 디버그 모드와 릴리즈 모드 전환
    • -GNinja
      • Generator 선택
    • -DCMAKE_INSTALL_PREFIX=../install
      • 로컬 파일 설치를 위한 경로 지정(내 경우 opencv_install)
    • ../opencv
      - 최상위 CMakeLists.txt가 위치한 디렉토리
      [Build 과정]
  • make -j
    • make
      • Make 빌드 실행
    • -j
      • 병렬빌드 작업의 수

[실습] Hello CMake

1. 기본적인 C++ 프로젝트의 폴더 구조를 확립한다.

  • CLion을 실행한 후 Home디렉토리에 hello_cmake폴더를 만들어주고 new project로 들어가서 언어는 c++14를 선택해준다. 그 후 create까지 해준다
    • 앞으로 build 할 때 마다 이 build 폴더에 생겨날 것이다.
    • 지난번처럼 굳이 build폴더를 만들고, DCmake build type, debug, make -j 이러한 과정을 거치지 않아도 CLion에서 알아서 다 해준다.
  • CMakeLists.txt를 살펴보자
    • 첫번째 줄
      • cmake를 사용하기 위해선, CMakeLists.txt를 돌리기 위한 cmake의 최소 버전을 명시를 해주어야 한다.
    • 두번째 줄
      • 프로젝트의 이름이다. hello_cmake라는것을 알 수 있다. 안에를 hello_cmake LANGUAGES CXX라고 해주면 C++프로젝트라는것을 명시해줄 수 있다.
    • 네번째 줄
      • set함수는 변수에 어떠한 값을 넣어주는 것이다.
      • 변수들 중에서 CMAKE_로 시작하는 변수들은 전부 CMAKE의 종속 변수들이다. 빌드를 위한 CMAKE의 설정값들을 바꾸어준다는 것인데 여기서는 CXX_STANDARD인데, 모든 C++의 버전을 의미한다. 14는 C++14를 빌드하겠다고 한 것이다.
      • set(CMAKE_CXX_STANDARD_REQUIRED ON)을 추가할 것이다.
        • C++14을 넘어가는(e.g 17,20,23) 기능들이 사용되는 경우엔 빌드를 제한하겠다는 것이다. 이게 없다고 하면 넘어가는 기능을 사용하면 warning정도만 뜨고 진행은 된다. 자율주행 개발에 있어선, 규제가 있어 C++11을 요구하거나 C++14을 요구하는 경우가 많기 때문에 14만으로 빌드를 하겠다는것을 명시한다.
    • 마지막 줄
      • add_executable은 실행파일을 의미한다. 우리가 이러한 실행파일을 만들 instruct를 추가하겠다 라는것을 의미한다.
      • 변수가 2개, hello_cmake와 main.cpp가 있다.
        • hello_cmake는 우리가 만들 실행파일의 이름이다. hello_cmake라는 프로그램을 만들것이다.
        • main.cpp는 프로그램을 만들 소스코드이다.
  • 이대로 빌드를 해보자(망치버튼, ctrl+F9)
    • 빌드가 완성된 모습
  • C++의 폴더 구조를 확립해보자
  • 첫번째로 필요한 것은 소스파일을 넣을둘 폴더이다. 2가지 방법이 있다.
      1. include 폴더를 만든 후에 안에는 headerfile을 넣고, 그리고 소스 폴더를 만든 후에 그 안에는 소스 파일을 넣어서 headerfile과 소스 파일을 분리 하는 방법이고
      1. modules 폴더 안에 module폴더를 만들고 각각의 모듈들의 폴더에다가 headerfile과 소스파일 둘 다 넣는 방법이다.
  • hello_cmake에서 우클릭 -> new -> directory로 modules폴더를 만들고, module1,2,3을 만들자.
    • 두 방법을 섞은, 각각의 모듈 폴더 안에(module1,module2,module3) include와 source폴더를 하나씩 만들어보자.(헤더와 소스를 분리)
    • 앞으로 이 모듈들은 라이브러리만 생성될 것이다.
  • 다음으론 examples폴더를 만들어주고, main문들만 존재하게 만들어주자.(main.cpp가 examples폴더로 가야된다.)
    • 앞으로 쓸 모든 코드들의 entry point들은 모두 examples에 있을 것이다.
  • 마지막으로 필요한것은 thirdparty 폴더이다.
    • 앞으로 사용할 모든 thirdparty library들이 이 thirdparty라는 폴더 안에 local하게 설치되어있을 예정이다.
  • 추후 필요에 따라
    • dockerfiles를 만들 수도 있고,
    • shell script를 넣어둔 script폴더를 만들어 줄 수도 있고
    • 실험에 필요한 다양한 config를 확인할 수 있는 config폴더를 만들 수 있고
    • 데이터를 저장할 수 있는 data폴더,
    • Documentation을 적어두는 docs폴더,
    • test파일들을 넣어둘 test폴더,
    • 여러가지 test에 사용되는 resource를 넣어둘 resources폴더 등이 있을 수 있다.
  • 이제 우리가 만들 C++프로젝트에 실제로 코드를 넣어 빌드를 해보자.

2. OpenCV를 사용하는 라이브러리를 빌드할 것이다.

  • module1을 opencv를 사용하는 라이브러리를 빌드하는것으로 만들자.
  • 우선 thirdparty폴더 안에 opencv를 build해주자.
  • 터미널을 열고 thirdparty폴더로 들어가준다.
  • 그 후 OpenCV라는 폴더를 만들어준다.
  • 그리고 그 안에 git clone을 해주자
  • 전에 했던 build폴더와 install 폴더를 만들고 install 폴더에 debug모드로 build를 해주자
    • 빌드 폴더 안에서 cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../install ../opencv
  • generate가 다 끝났다면 make -j로 빌드를 실행한다.(-j는 에러가 발생하여 -j2로 진행하였음)
  • build가 다 됐으면 sudo make install을 통해서 install을 해준다.
    • install까지 하면 install 폴더 아래에 폴더가 생성된것을 볼 수 있다.
  • opencv가 다 깔렸으니 module1을 만들어보자.
  • 간단하게 module1에는 클래스 하나를 만들고, 그 클래스에 멤버 변수로는 cv::Mat을 넣어서 OpenCV를 사용하는 클래스를 만들려 한다.
  • include부터 작업을 시작하자.
  • module1폴더 안에 include폴더 안에 module1폴더를 만들고 그 안에 헤더 파일을 만들어주자.(ClassMat.hpp)
  • 안에 간단한 ClassMat에 대한 정의를 작성하자

  • 다음은 source파일을 작성해보자
    • 이때 2가지의 문제가 있다.
        1. ClassMat.hpp에서 에러가 하나 있다. Use of undeclared identifier 'cv'
        • opencv를 찾지 못했기 때문에 나타나는 현상. 아직 선언이 되지 않았음
        1. ClassMat.cpp에서 ClassMat.hpp를 찾지 못했음
        • 아직 cmake로 프로젝트를 엮어주지 않았기 때문에 나타나는 현상
  • cmake로 프로젝트를 엮어주자
  • 가장 최상위단에서 프로젝트를 엮어주자.
  • 가장 최상위단의 CMakeLists로 이동을 한다.
    • add_subdirectory(modules)를 추가해준다.
    • subdirectory는 지정한 directory에 있는 CMakeLists.txt를 실행하겠다는것을 의미한다.
      • modules라는 폴더에 있는 CMakeLists.txt를 실행하겠다는것을 의미한다.
      • 하지만 아직 modules라는 폴더 안에 CMakeLists.txt가 없다.
      • 그러니 modules에 CMakeLists.txt를 생성해주자.
    • modules폴더 안에는 module1,module2,module3가 있는데, 차례로 모듈들 마다 CMakeList를 실행할 수 있도록 addsubdirectory를 넣어주면 된다.
    • 하지만 지금은 module1만 할것이기 때문에 module1에 대한 subdirectory만 실행할 것이다.
    • module1에 add_subdirectory가 들어갔지만 module1에는 아직 CMakeLists.txt가 없다.
    • 그러니 module1에도 CMakeLists.txt를 만들어 주자.
  • module1자체가 하나의 library가 될 예정이니 이 역시 하나의 C++ 프로젝트라고 볼 수 있을것이다.
  • 다음은 소스파일들에 대해서 정리를 해준다.
  • MODULE1_SOURCE_FILES라는 변수를 만들어보자
    • set(MODULE1_SOURCE_FILES)
  • 다른 cpp파일들이 생긴다면 아래와 같이 작성해주면 된다.
  • 이 소스 파일들을 기반으로 library를 빌드할 명령어를 추가해주자.
    • add_library(), 라이브러리의 이름은 module1
    • 변수를 사용할땐 ${}안에 적어주면 된다.
  • 소스파일들은 넣었지만 소스파일들을 사용할 헤더파일들을 엮어보자
  • module1 library에 헤더파일들을 엮기 위해선
      1. include_directories()
      • 이 방법으로 하면 global, 모든 하위 CMakeLists에 대해서 적용이 될 수 있고
      1. target_include_directories()
      • 이 방법으로 하면 특정 타겟만을 대상으로 한다.
        • PRIVATE, PUBLIC, INTERFACE.. 등이 있다
      • 하나하나 해줄 필요 없이 경로만 지정하면 된다.
  • 다 됐다면 CMake tap에서 reload를 해주자(generate 과정을 거치는 것)
  • 우측 상단을 보면 hello_cmake 외에도 module1이 생긴것을 볼 수 있다.
  • 이 두 CMakeLists.txt의 차이는(hello_cmake, module1)
  • 최상위단에선 add_executable, 실행파일을 만드는 것 즉, output으로 나오는것이 지금까지는 hello_cmake였고 이것밖에 없어서 CLion은 자동으로 지정을 해준 것인데 방금 add_library를 적었기 때문에 library를 build해주는 옵션인 module1도 생기게 되었다.
  • 이를 토대로 build를 해주자
    • 에러가 난다. cv라는 namespace를 몰라서 생기는 문제
    • source파일과 header파일이 연결이 되어서 headerfile의 내용까지 검사가 들어갔다.
    • library를 빌드하려면 source 파일들이 연결이 되어야 하고 soucefile들은 headerfile들을 찾아가는데 지금 headerfile에서 에러가 난것을 보면 library build, 우리가 준 command에서 sourcefile들을 정확히 찾았고 sourcefile들은 headerfile들을 정확하게 찾은것을 볼 수 있다.
  • 이제 OpenCV를 연결해보자
  • OpenCV를 연결하는것도 CMake로 가능하다. 그러기 위해서는 먼저 OpenCV를 찾아야 한다.
  • thirdparty OpenCV install 폴더에 설치를 해뒀다.
  • thirdparty library를 찾는것은 CMake에서는 find_package()라는 함수를 사용한다.
    • find_package(OpenCV REQUIRED) REQUIRED라는 키워드를 사용한다.
      - REQUIRED라는 키워드는 OpenCV를 찾지 못하면 build를 하지 않겠다 라는 것을 의미한다.
      - 유의해야 할 점은find_package()함수는 system에 설치된 library들만 먼저 찾는다.
      - 우리는 local설치를 했기 때문에 경로를 따로 지정해주어야 한다.
      - 그렇기에, HINTS ${}를 사용해준다.
      - CMAKE_SOURCE_DIR는 CMakeLists를 호출하는 가장 최상위단 CMake build의 entry의 디렉토리를 의미한다.(hello_cmake폴더가 될 것)
  • 그 후 빌드를 해준다.
  • 그리고 OpenCV가 정말로 찾아졌는지 확인을 해준다.
if (OpenCV_FOUND)
	message(STATUS "OpenCV Found! - ${OpenCV_DIR}")
endif()

  • reload를 하고 build를 다시 해주면
    • generate단계에서 OpenCV를 찾고 그 경로를 보여준다.
    • OpenCV를 제대로 찾은것이다.
  • include directory에 OpenCV를 include를 해준다.
  • 그 후 library를 link 해준다
    • target_link_libraries(module1 PRIVATE ${OpenCV_LIBS})
  • 헤더파일에서 opencv2를 include해준다
    • cv에 있던 빨간줄이 사라진것을 볼 수 있다.
  • 헤더파일도 연결이 되었고 libray도 linking이 됐고 build도 잘 된것을 확인할 수 있다.

3. OpenCV를 사용하는 프로그램을 하나 빌드할 것이다.

  • 우선 example폴더 안에 main문 하나를 만든다.

  • 다시 돌아와서 최상위 단의 CMakeLists.txt로 돌아간다.
  • add_executalbe()을 하나 추가해준다; 실행파일의 이름은 exec_module1이라고 해준다
  • add_executable(exec_module1)
    • reload를 해주면 exec_module1이 생긴것을 볼 수 있다.
  • 실행파일만 만들었지만, 아직 link가 잘 안되어 있는 것을 볼 수 있다
  • target_link_libraries를 통해 link를 해준다.
  • reload를 해준 후 build를 해준다.
    • generate단계에서는 문제가 없다.
  • 하지만 build에서 보면 include에서 문제가 생긴것을 볼 수 있다.
    • include를 따로 직접 해도 괜찮고
    • 더 좋은 방법은 module1/CMakeLists.txt에서 target_include_directories에서 include directories를 PRIVATE로 되어있는 것을 -> PRIVATE로 바꾸어준다.
  • build가 잘 된것을 볼 수 있고
  • 실행을 하면
    • success까지 뜬것을 볼 수 있다.

4. OpenCV와 eigen3 library를 사용하는 프로그램을 빌드할 것이다.

  • eigen3는 build하지 않았으니 가장 먼저 build 해야한다.
  • source파일을 가져와서 build를 해주도록 하자



  • module2에 Eigen3를 래핑하는 library를 만들어보자.
  • module2는 module1을 복사를 해서 아까와 비슷한 과정을 거쳐서 만들도록 하자
  • modules CMakeLists.txt에 add_subdirectory(module2)를 추가하고
  • cmake를 돌리면
    • module2가 잘 생긴것을 볼 수 있다.
    • ClassEigenMat.hpp까지 잘 작성을 하였다면 module2를 build를 해보자
  • 잘 빌드가 되었다.
  • module1과 module2를 사용하는 프로그램을 만들어보자
  • examples -> new -> sourcefile
  • 가장 상위단의 CMakeLists.txt를 수정해준다.
  • exec_module1_module2.cpp를 마저 작성해준다
    • module1은 link가 된적이 있기때문에 에러가 안뜨지만 module2는 link가 된적이 없기 때문에 에러가 난다.
  • reload를 한번 해주고(exec_module1_module2가 생성됨)
  • exec_module1_module2를 빌드를 해준다.
  • build가 잘 된 것을 볼 수 있다.
  • 실행을 해보면
    • 성공적으로 Success가 뜨는것을 볼 수 있다.

폴더 구조

CMakeLists.txt command

  • add_subdirectory: 하위단의 CMakeLists.txt을 읽기 위해 사용
  • add_executale, add_library: 각각 실행파일와 라이브러리를 생성하기위해 사용
  • find_package: thirdparty 라이브러리들을 로딩하기위해 사용
  • target_include_directories: 특정 생성하는 파일이나 라이브러리에 헤더 파일을 연결하는 기능을 한다.
  • target_link_libraries: 타겟 파일 또는 타겟 라이브러리에 라이브러리를 link해준다
  • message: 디버깅을 위한 용도

0개의 댓글